diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..cbe62527 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,305 @@ +# To learn more about .editorconfig see https://aka.ms/editorconfigdocs +############################### +# Core EditorConfig Options # +############################### +root = true +# All files +[*] +indent_style = space +end_of_line = lf + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +tab_width = 2 +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +# JSON config files +[*.json] +tab_width = 2 +indent_size = 2 +insert_final_newline = false +trim_trailing_whitespace = true + +# Stylesheet files +[*.{css,scss,sass,less}] +insert_final_newline = true +trim_trailing_whitespace = true +tab_width = 4 +indent_size = 4 + +# Code files +[*.{cs,csx,vb,vbx}] +tab_width = 4 +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8-bom +file_header_template = Copyright (c) Microsoft. All rights reserved. + +############################### +# Java Coding Conventions # +############################### +[*.java] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +tab_width = 4 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_smart_tabs = false +ij_visual_guides = none + +max_line_length = 100 +ij_continuation_indent_size = 4 +ij_formatter_tags_enabled = false +ij_wrap_on_typing = false + +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = false +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = false +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_resources = false +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = normal +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = true +ij_java_binary_operation_wrap = normal +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 1 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_at_first_column = true +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = normal +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 999 +ij_java_class_names_in_javadoc = 1 +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_while_brace_force = always +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_entity_dd_suffix = EJB +ij_java_entity_eb_suffix = Bean +ij_java_entity_hi_suffix = Home +ij_java_entity_lhi_prefix = Local +ij_java_entity_lhi_suffix = Home +ij_java_entity_li_prefix = Local +ij_java_entity_pk_class = java.lang.String +ij_java_entity_vo_suffix = VO +ij_java_enum_constants_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = normal +ij_java_field_annotation_wrap = split_into_lines +ij_java_finally_on_new_line = false +ij_java_for_brace_force = always +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = normal +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_if_brace_force = always +ij_java_imports_layout = $*, |, * +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = true +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 1 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_control_statement_in_one_line = false +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_at_first_column = true +ij_java_message_dd_suffix = EJB +ij_java_message_eb_suffix = Bean +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = normal +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = normal +ij_java_modifier_list_wrap = false +ij_java_names_count_to_use_import_on_demand = 999 +ij_java_parameter_annotation_wrap = off +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_session_dd_suffix = EJB +ij_java_session_eb_suffix = Bean +ij_java_session_hi_suffix = Home +ij_java_session_lhi_prefix = Local +ij_java_session_lhi_suffix = Home +ij_java_session_li_prefix = Local +ij_java_session_si_suffix = Service +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_subclass_name_suffix = Impl +ij_java_ternary_operation_signs_on_next_line = true +ij_java_ternary_operation_wrap = normal +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = normal +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = always +ij_java_while_on_new_line = false +ij_java_wrap_comments = true +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..b5845d14 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Auto-detect text files, ensure they use LF. +* text=auto eol=lf working-tree-encoding=UTF-8 + +# Bash scripts +*.sh text eol=lf +*.cmd text eol=crlf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..d6e7dac3 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# @microsoft/octo-semantickernel-pr-java owns any files in the java +# directory at the root of the repository and any of its +# subdirectories. +/ @microsoft/octo-semantickernel-pr-java diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..6f781500 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,36 @@ +--- +name: Bug report +about: Create a report to help us improve +title: 'Bug: ' +labels: ["bug", "triage"] +projects: ["semantic-kernel"] +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Maven** + - Version: [e.g. 1.1.5] + - Dependencies: list of semantic-kernel related dependencies in your `pom.xml` + +**Platform** + - IDE: [e.g. IntelliJ, Eclipse, VS Code] + - JDK version: [e.g. JDK 11.0.17] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_graduation.md b/.github/ISSUE_TEMPLATE/feature_graduation.md new file mode 100644 index 00000000..37d207ea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_graduation.md @@ -0,0 +1,29 @@ +--- +name: Feature graduation +about: Plan the graduation of an experimental feature +title: 'Graduate XXX feature' +labels: ["feature_graduation"] +projects: ["semantic-kernel"] +assignees: '' + +--- + +--- +name: Feature graduation +about: Plan the graduation of an experimental feature + +--- + +Checklist to be completed when graduating an experimental feature + +- [ ] Notify PM's and EM's that feature is read for graduation +- [ ] Contact PM for list of sample use cases +- [ ] Verify there are sample implementations​ for each of the use cases +- [ ] Verify telemetry and logging are complete +- [ ] ​Verify API docs are complete and arrange to have them published +- [ ] Make appropriate updates to Learn docs​ +- [ ] Make appropriate updates to Concept samples +- [ ] Male appropriate updates to Blog posts +- [ ] Verify there are no serious open Issues​​ +- [ ] Update table in EXPERIMENTS.md +- [ ] Remove SKEXP​ flag from the experimental code diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..ca7db808 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: 'New Feature: ' +labels: '["triage", "enhancement"]' +projects: ["semantic-kernel"] +assignees: '' + +--- + +--- +name: Feature request +about: Suggest an idea for this project + +--- + + + + + + diff --git a/.github/_typos.toml b/.github/_typos.toml new file mode 100644 index 00000000..a92576ba --- /dev/null +++ b/.github/_typos.toml @@ -0,0 +1,44 @@ +# Typos configuration file +# +# Info: https://github.com/marketplace/actions/typos-action +# Install: brew install typos-cli +# Install: conda install typos +# Run: typos -c .github/_typos.toml + +[files] +extend-exclude = [ + "_typos.toml", + "package-lock.json", + "*.bicep", + "encoder.json", + "vocab.bpe", + "CodeTokenizerTests.cs", + "test_code_tokenizer.py", + "*response.json", + "samples/semantickernel-demos/sk-presidio-sample/README.md" +] + +[default.extend-words] +ACI = "ACI" # Azure Container Instance +exercize = "exercize" # test typos +gramatical = "gramatical" # test typos +Guid = "Guid" # Globally Unique Identifier +HD = "HD" # Test header value +EOF = "EOF" # End of File +ans = "ans" # Short for answers +arange = "arange" # Method in Python numpy package +prompty = "prompty" # prompty is a format name. +ist = "ist" # German language + +[default.extend-identifiers] +ags = "ags" # Azure Graph Service + +[type.jupyter] +extend-ignore-re = [ + '"[A-Fa-f0-9]{8}"', # cell id strings +] + +[type.msbuild] +extend-ignore-re = [ + 'Version=".*"', # ignore package version numbers +] diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..97b4f664 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,58 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Maintain dependencies for nuget + - package-ecosystem: "nuget" + directory: "dotnet/" + schedule: + interval: "weekly" + day: "monday" + ignore: + # For all System.* and Microsoft.Extensions/Bcl.* packages, ignore all major version updates + - dependency-name: "System.*" + update-types: ["version-update:semver-major"] + - dependency-name: "Microsoft.Extensions.*" + update-types: ["version-update:semver-major"] + - dependency-name: "Microsoft.Bcl.*" + update-types: ["version-update:semver-major"] + - dependency-name: "Moq" + labels: + - ".NET" + - "dependencies" + + # Maintain dependencies for nuget + - package-ecosystem: "nuget" + directory: "samples/dotnet" + schedule: + interval: "weekly" + day: "monday" + + # Maintain dependencies for npm + - package-ecosystem: "npm" + directory: "samples/apps" + schedule: + interval: "weekly" + day: "monday" + + # Maintain dependencies for pip + - package-ecosystem: "pip" + directory: "python/" + schedule: + interval: "weekly" + day: "monday" + labels: + - "python" + - "dependencies" + + # Maintain dependencies for github-actions + - package-ecosystem: "github-actions" + # Workflow files stored in the + # default location of `.github/workflows` + directory: "/" + schedule: + interval: "weekly" + day: "monday" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..38e62262 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,23 @@ +### Motivation and Context + + + +### Description + + + +### Contribution Checklist + + + +- [ ] The code builds clean without any errors or warnings +- [ ] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations +- [ ] All unit tests pass, and I have added new tests where possible +- [ ] I didn't break anyone :smile: diff --git a/.github/workflows/close-inactive-issues.yml b/.github/workflows/close-inactive-issues.yml new file mode 100644 index 00000000..10244c76 --- /dev/null +++ b/.github/workflows/close-inactive-issues.yml @@ -0,0 +1,22 @@ +name: Close inactive issues +on: + schedule: + - cron: "30 1 * * *" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v5 + with: + days-before-issue-stale: 90 + days-before-issue-close: 14 + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has been open for 90 days with no activity." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..caf56980 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,78 @@ +# CodeQL is the code analysis engine developed by GitHub to automate security checks. +# The results are shown as code scanning alerts in GitHub. For more details, visit: +# https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/about-code-scanning-with-codeql + +name: "CodeQL" + +on: + push: + branches: ["main"] + schedule: + - cron: "17 11 * * 2" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ["java"] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + if: ${{ matrix.language != 'java' }} + uses: github/codeql-action/autobuild@v2 + + - name: Setup JDK + uses: actions/setup-java@v4 + if: ${{ matrix.language == 'java' }} + with: + java-version: 17 + distribution: microsoft + cache: maven + + - name: Build Java + if: ${{ matrix.language == 'java' }} + run: ./mvnw -B -DskipTests -Pcompile-jdk17 clean install --file pom.xml + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/java-build.yml b/.github/workflows/java-build.yml new file mode 100644 index 00000000..d4b487cf --- /dev/null +++ b/.github/workflows/java-build.yml @@ -0,0 +1,63 @@ +name: Build Java Semantic Kernel + +# Triggers the workflow on manual dispatch, push, and pull request events +# for the specified branches and paths +on: + workflow_dispatch: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + # Builds and tests the Java project + java-build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Defines a matrix strategy for JDK versions 8 and 17 + java-versions: [8, 17] + + name: Java CI on JDK${{ matrix.java-versions }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # Need to use JDK 11 to build for JDK 8 + - name: Set JDK + id: set-jdk + shell: bash + run: | + if [[ ${{ matrix.java-versions }} == 8 ]]; then + echo "JDK_VERSION=11" >> $GITHUB_OUTPUT + else + version=${{ matrix.java-versions }} + echo "JDK_VERSION=$version" >> $GITHUB_OUTPUT + fi + + # Sets up the specified JDK version from the matrix + - uses: actions/setup-java@v4 + with: + java-version: ${{ steps.set-jdk.outputs.JDK_VERSION }} + distribution: microsoft + cache: maven + + # Builds the project with Maven using the matrix JDK version + - name: Build with Maven + run: ./mvnw -B -Pbug-check -DskipTests -Pcompile-jdk${{ matrix.java-versions }} clean install --file pom.xml + + # Runs tests with Maven using the matrix JDK version + - name: Run tests + run: ./mvnw -B -Pbug-check -Pcompile-jdk${{ matrix.java-versions }} test --file pom.xml + + # Uploads test artifacts for each JDK version + - uses: actions/upload-artifact@v2 + if: always() + with: + name: test_output_sk_jdk${{ matrix.java-versions }}u + path: ./**/target/surefire-reports/*Test.txt diff --git a/.github/workflows/java-integration-tests.yml b/.github/workflows/java-integration-tests.yml new file mode 100644 index 00000000..a65ee5cc --- /dev/null +++ b/.github/workflows/java-integration-tests.yml @@ -0,0 +1,64 @@ +name: Run Java Integration Tests and Samples + +on: + workflow_dispatch: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + java-integration-tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + java-versions: [8, 17] + + name: Java Tests on JDK${{ matrix.java-versions }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # Need to use JDK 11 to build for JDK 8 + - name: Set JDK + id: set-jdk + shell: bash + run: | + if [[ ${{ matrix.java-versions }} == 8 ]]; then + echo "JDK_VERSION=11" >> $GITHUB_OUTPUT + else + version=${{ matrix.java-versions }} + echo "JDK_VERSION=$version" >> $GITHUB_OUTPUT + fi + + - uses: actions/setup-java@v4 + with: + java-version: ${{ steps.set-jdk.outputs.JDK_VERSION }} + distribution: microsoft + cache: maven + + - name: Build with Maven + run: ./mvnw -B -Pwith-samples -Pbug-check -DskipTests -Pcompile-jdk${{ matrix.java-versions }} clean install --file pom.xml + if: ${{ matrix.java-versions >= 17 }} + + # API tests run on JDK 17+ + - name: Run integration tests + run: ../mvnw -B -Pbug-check clean install --file pom.xml + working-directory: api-test + env: + OPENAI_API_KEY: "" + AZURE_OPENAI_ENDPOINT: "" + AZURE_OPENAI_API_KEY: "" + AZURE_OPENAI_DEPLOYMENT_NAME: "" + if: ${{ matrix.java-versions >= 17 }} + + # Samples build on JDK 17+ + - name: Build semantic-kernel samples + run: ../mvnw -B clean install --file pom.xml + working-directory: samples + if: ${{ matrix.java-versions >= 17 }} diff --git a/.github/workflows/java-publish-package.yml b/.github/workflows/java-publish-package.yml new file mode 100644 index 00000000..25afec46 --- /dev/null +++ b/.github/workflows/java-publish-package.yml @@ -0,0 +1,41 @@ +name: Deploy Java Semantic Kernel Package + +# Triggers the workflow on merging a PR +on: + pull_request: + types: + - closed + branches: [ "main" ] + +permissions: + contents: read + packages: write + +jobs: + if_merged: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + # Sets up the specified JDK version from the matrix + - uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: microsoft + cache: maven + + - name: Build artifacts + run: ./mvnw -B -DskipTests -Pcompile-jdk8 -P-compile-jdk17 clean deploy --file pom.xml -DaltDeploymentRepository=local::file:///tmp/target/staging-deploy + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: Artifacts + path: /tmp/target/staging-deploy + + - name: Deploy to github packages + run: ./mvnw -B -DskipTests -Pcompile-jdk8 -P-compile-jdk17 -Pgithub-packages clean deploy --file pom.xml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/label-issues.yml b/.github/workflows/label-issues.yml new file mode 100644 index 00000000..42806188 --- /dev/null +++ b/.github/workflows/label-issues.yml @@ -0,0 +1,54 @@ +name: Label issues +on: + issues: + types: + - reopened + - opened + +jobs: + label_issues: + name: "Issue: add labels" + if: ${{ github.event.action == 'opened' || github.event.action == 'reopened' }} + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }} + script: | + // Get the issue body and title + const body = context.payload.issue.body + let title = context.payload.issue.title + + // Define the labels array + let labels = ["triage"] + + // Check if the body or the title contains the word 'python' (case-insensitive) + if ((body != null && body.match(/python/i)) || (title != null && title.match(/python/i))) { + // Add the 'python' label to the array + labels.push("python") + } + + // Check if the body or the title contains the word 'java' (case-insensitive) + if ((body != null && body.match(/java/i)) || (title != null && title.match(/java/i))) { + // Add the 'java' label to the array + labels.push("java") + } + + // Check if the body or the title contains the words 'dotnet', '.net', 'c#' or 'csharp' (case-insensitive) + if ((body != null && body.match(/.net/i)) || (title != null && title.match(/.net/i)) || + (body != null && body.match(/dotnet/i)) || (title != null && title.match(/dotnet/i)) || + (body != null && body.match(/C#/i)) || (title != null && title.match(/C#/i)) || + (body != null && body.match(/csharp/i)) || (title != null && title.match(/csharp/i))) { + // Add the '.NET' label to the array + labels.push(".NET") + } + + // Add the labels to the issue + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: labels + }); diff --git a/.github/workflows/label-title-prefix.yml b/.github/workflows/label-title-prefix.yml new file mode 100644 index 00000000..f443c980 --- /dev/null +++ b/.github/workflows/label-title-prefix.yml @@ -0,0 +1,73 @@ +name: Label title prefix +on: + issues: + types: [labeled] + pull_request_target: + types: [labeled] + +jobs: + add_title_prefix: + name: "Issue/PR: add title prefix" + continue-on-error: true + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/github-script@v7 + name: "Issue/PR: update title" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + let prefixLabels = { + "python": "Python", + "java": "Java", + ".NET": ".Net" + }; + + function addTitlePrefix(title, prefix) + { + // Update the title based on the label and prefix + // Check if the title starts with the prefix (case-sensitive) + if (!title.startsWith(prefix + ": ")) { + // If not, check if the first word is the label (case-insensitive) + if (title.match(new RegExp(`^${prefix}`, 'i'))) { + // If yes, replace it with the prefix (case-sensitive) + title = title.replace(new RegExp(`^${prefix}`, 'i'), prefix); + } else { + // If not, prepend the prefix to the title + title = prefix + ": " + title; + } + } + + return title; + } + + labelAdded = context.payload.label.name + + // Check if the issue or PR has the label + if (labelAdded in prefixLabels) { + let prefix = prefixLabels[labelAdded]; + switch(context.eventName) { + case 'issues': + github.rest.issues.update({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + title: addTitlePrefix(context.payload.issue.title, prefix) + }); + break + + case 'pull_request_target': + github.rest.pulls.update({ + pull_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + title: addTitlePrefix(context.payload.pull_request.title, prefix) + }); + break + default: + core.setFailed('Unrecognited eventName: ' + context.eventName); + } + } diff --git a/.github/workflows/markdown-link-check-config.json b/.github/workflows/markdown-link-check-config.json new file mode 100644 index 00000000..50ada491 --- /dev/null +++ b/.github/workflows/markdown-link-check-config.json @@ -0,0 +1,39 @@ +{ + "ignorePatterns": [ + { + "pattern": "/github/" + }, + { + "pattern": "./actions" + }, + { + "pattern": "./blob" + }, + { + "pattern": "./issues" + }, + { + "pattern": "./discussions" + }, + { + "pattern": "./pulls" + }, + { + "pattern": "^http://localhost" + }, + { + "pattern": "^https://localhost" + }, + { + "pattern": "^https://platform.openai.com" + }, + { + "pattern": "^https://outlook.office.com/bookings" + } + ], + "timeout": "20s", + "retryOn429": true, + "retryCount": 3, + "fallbackRetryDelay": "30s", + "aliveStatusCodes": [200, 206, 429, 500, 503] +} diff --git a/.github/workflows/markdown-link-check.yml b/.github/workflows/markdown-link-check.yml new file mode 100644 index 00000000..c0993625 --- /dev/null +++ b/.github/workflows/markdown-link-check.yml @@ -0,0 +1,23 @@ +name: Check .md links + +on: + workflow_dispatch: + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + markdown-link-check: + runs-on: ubuntu-latest + # check out the latest version of the code + steps: + - uses: actions/checkout@v4 + + # Checks the status of hyperlinks in .md files in verbose mode + - name: Check links + uses: gaurav-nelson/github-action-markdown-link-check@v1 + with: + use-verbose-mode: "yes" + config-file: ".github/workflows/markdown-link-check-config.json" diff --git a/.github/workflows/merge-gatekeeper.yml b/.github/workflows/merge-gatekeeper.yml new file mode 100644 index 00000000..adb6811a --- /dev/null +++ b/.github/workflows/merge-gatekeeper.yml @@ -0,0 +1,30 @@ +name: Merge Gatekeeper + +on: + pull_request: + branches: [ "main", "feature*" ] + merge_group: + branches: ["main"] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + merge-gatekeeper: + runs-on: ubuntu-latest + # Restrict permissions of the GITHUB_TOKEN. + # Docs: https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs + permissions: + checks: read + statuses: read + steps: + - name: Run Merge Gatekeeper + # NOTE: v1 is updated to reflect the latest v1.x.y. Please use any tag/branch that suits your needs: + # https://github.com/upsidr/merge-gatekeeper/tags + # https://github.com/upsidr/merge-gatekeeper/branches + uses: upsidr/merge-gatekeeper@v1 + if: github.event_name == 'pull_request' + with: + token: ${{ secrets.GITHUB_TOKEN }} + timeout: 3600 diff --git a/.github/workflows/typos.yaml b/.github/workflows/typos.yaml new file mode 100644 index 00000000..d7a937ba --- /dev/null +++ b/.github/workflows/typos.yaml @@ -0,0 +1,29 @@ +# Check pull requests for typos. +# +# Configuration: .github/_typos.toml +# +# Info: https://github.com/marketplace/actions/typos-action +# Local install: brew install typos-cli +# Local install: conda install typos +# Local run: typos -c .github/_typos.toml + +name: Spell Check + +on: + workflow_dispatch: + pull_request: + branches: [ "main" ] + +jobs: + run: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Use custom config file + uses: crate-ci/typos@master + with: + config: .github/_typos.toml + write_changes: false diff --git a/.github/workflows/update-version.sh b/.github/workflows/update-version.sh new file mode 100755 index 00000000..5db3622f --- /dev/null +++ b/.github/workflows/update-version.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +POSITIONAL_ARGS=() + +while [[ $# -gt 0 ]]; do + case $1 in + -f|--file) + file="$2" + shift # past argument + shift # past value + ;; + -p|--propsFile) + propsFile="$2" + shift # past argument + shift # past value + ;; + -b|--buildAndRevisionNumber) + buildAndRevisionNumber="$2" + shift # past argument + shift # past value + ;; + -*|--*) + echo "Unknown option $1" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") # save positional arg + shift # past argument + ;; + esac +done + +set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters + +if [ -z "$file" ]; then + echo "ERROR: Parameter file (-f|--file) not provided" + exit 1; +elif [ ! -f "$file" ]; then + echo "ERROR: file ${file} not found" + exit 1; +fi + +if [ -n "$(cat $file | grep -i "false")" ]; then + echo "Project is marked as NOT packable - skipping." + exit 0; +fi + +if [ -z "$propsFile" ]; then + echo "ERROR: Parameter propsFile (-f|--file) not provided" + exit 1; +elif [ ! -f "$propsFile" ]; then + echo "ERROR: propsFile ${file} not found" + exit 1; +fi + +if [ -z "$buildAndRevisionNumber" ]; then + echo "ERROR: Parameter buildAndRevisionNumber (-b|--buildAndRevisionNumber) not provided" + exit 1; +fi + +propsVersionString=$(cat $propsFile | grep -i ""); +regex="([0-9.]*)<\/Version>" +if [[ $propsVersionString =~ $regex ]]; then + propsVersion=${BASH_REMATCH[1]} +else + echo "ERROR: Version tag not found in propsFile" + exit 1; +fi + +if [ -z "$propsVersion" ]; then + echo "ERROR: Version tag not found in propsFile" + exit 1; +elif [[ ! "$propsVersion" =~ ^0.* ]]; then + echo "ERROR: Version expected to start with 0. Actual: ${propsVersion}" + exit 1; +fi + +fullVersionString="${propsVersion}.${buildAndRevisionNumber}-preview" + +if [[ ! "$fullVersionString" =~ ^0.* ]]; then + echo "ERROR: Version expected to start with 0. Actual: ${fullVersionString}" + exit 1; +fi + +echo "==== Project: ${file} ===="; +echo "propsFile = ${propsFile}" +echo "buildAndRevisionNumber = ${buildAndRevisionNumber}" +echo "version prefix from propsFile = ${propsVersion}" +echo "full version string: ${fullVersionString}" + +versionInProj=$(cat $file | grep -i ""); +if [ -n "$versionInProj" ]; then + # Version tag already exists in the csproj. Let's replace it. + echo "Updating version tag..." + content=$(cat $file | sed --expression="s/\([0-9]*.[0-9]*\)<\/Version>/$fullVersionString<\/Version>/g"); +else + # Version tag not found in the csproj. Let's add it. + echo "Project is packable - adding version tag..." + content=$(cat $file | sed --expression="s/<\/Project>/$fullVersionString<\/Version><\/PropertyGroup><\/Project>/g"); +fi + +if [ $? -ne 0 ]; then exit 1; fi +echo "$content" && echo "$content" > $file; +if [ $? -ne 0 ]; then exit 1; fi + +echo "DONE"; +echo ""; diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e6f26223 --- /dev/null +++ b/.gitignore @@ -0,0 +1,63 @@ +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# JetBrains IntelliJ +.idea +*.ipr +*.iml +*.iws + +# Maven settings +conf.properties +java/**/target +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# Eclipse m2e generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Other +.env +certs/ +launchSettings.json +config.development.yaml +*.development.config +*.development.json +.DS_Store +node_modules/ +obj/ +bin/ +_dev/ +.dev/ +*.devis.* +.vs/ +*.user +**/.vscode/chrome +**/.vscode/.ropeproject/objectdb +*.pyc +.ipynb_checkpoints +.jython_cache/ +__pycache__/ +.mypy_cache/ +__pypackages__/ +.pdm.toml +global.json diff --git a/java/.mvn/jvm.config b/.mvn/jvm.config similarity index 100% rename from java/.mvn/jvm.config rename to .mvn/jvm.config diff --git a/java/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from java/.mvn/wrapper/maven-wrapper.properties rename to .mvn/wrapper/maven-wrapper.properties diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..d24ed88a --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "ms-java.vscode-java-pack", + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..bd5534a5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,5 @@ +{ + "version": "0.2.0", + "configurations": [ + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..16667e41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,25 @@ +{ + "editor.formatOnType": true, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.bracketPairColorization.enabled": true, + "editor.guides.bracketPairs": "active", + "notebook.output.textLineLimit": 500, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true + }, + "[java]": { + "editor.formatOnSave": false, + "editor.tabSize": 4, + "editor.codeActionsOnSave": { + "source.fixAll": "never" + }, + }, + "java.debug.settings.onBuildFailureProceed": true, + "java.compile.nullAnalysis.mode": "disabled" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..feea3bb0 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,11 @@ +{ + "version": "2.0.0", + "inputs": [ + { + "id": "filter", + "type": "promptString", + "default": "", + "description": "Enter a filter to pass as argument or filter" + } + ] +} diff --git a/java/README.md b/BUILD.md similarity index 97% rename from java/README.md rename to BUILD.md index 190eb7f2..7c45218d 100644 --- a/java/README.md +++ b/BUILD.md @@ -111,8 +111,8 @@ Make sure your pull request has an objective title and a clear description expla ## License -This project is licensed under the [MIT License](../LICENSE). +This project is licensed under the [MIT License](LICENSE). ## Code of Conduct -This project has adopted the [Microsoft Open Source Code of Conduct](../CODE_OF_CONDUCT.md). +This project has adopted the [Microsoft Open Source Code of Conduct](CODE_OF_CONDUCT.md). diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..3ad56c82 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,59 @@ +# 1.2.0 + +- Add ability to use image_url as content for a OpenAi chat completion + - As part of this `ChatMessageTextContent` and `ChatMessageImageContent` was added that extends the + existing `ChatMessageContent` class. `ChatMessageContent` for now defaults to a text content type for backwards + compatibility. However, users are encouraged to migrate to using the builders on `ChatMessageTextContent` to + create text based chat messages. + - Constructors of `ChatMessageContent` were also modified to support this change. +- Added preliminary hugging face implementation that is still in development/beta. +- Added Gemini support +- Added OpenTelemetry spans for OpenAI requests +- Update the user agent for OpenAI requests +- Move XML parsing classes to implementation package as they are not expected to be used by users. + +#### Non-API Changes + +- Reorganized the repository when moving to the new Github location +- Removed non-Java files +- Update readmes +- Update build scripts +- Bring back Spring example project + +# 1.1.5 + +- Fix bug with removing new lines on function parameters on Windows +- Fix bug forming serializing arguments to tool calls + +# 1.1.3 + +- Fix bug appending plugin name to tool calls +- Improve exception handling in OpenAIChatCompletion + +# 1.1.2 + +- Upgrade azure-identity to 1.12.1 +- Remove fixed netty version in bom + +# 1.1.1 + +- Upgrade azure-ai-openai to 1.0.0-beta.8 + +# 1.1.0 + +### Breaking Changes + +- `ChatHistory` no longer has a default message, see below for more details. + +### Api Changes + +- Allow setting deployment name in addition to modelId on AI services. +- Remove default message of "Assistant is a large language model" from ChatHistory + - **This is a breaking change if you were relying on the default message in your code** +- Add InvocationReturnMode and rework OpenAi chat completion to allow configuring what data is returned from Chat + requests + +### Other + +- Reorganize example projects and documentation structure. +- Number of sample updates and bug fixes. \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..f9ba8cf6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/COMMUNITY.md b/COMMUNITY.md new file mode 100644 index 00000000..be98d425 --- /dev/null +++ b/COMMUNITY.md @@ -0,0 +1,32 @@ +# Welcome to the Semantic Kernel Community! + +Below are some ways that you can get involved in the SK Community. + +## Engage on Github + +File issues, submit PRs, and provide feedback and ideas to what you'd like to see from the Semantic Kernel. +We do our best to respond to each submission. + +## Public Community Office Hours + +We regularly have Community Office Hours that are open to the **public** to join. + +Add Semantic Kernel events to your calendar - we're running two community calls to cater different timezones for Q&A Office Hours: +* Americas timezone: download the [calendar.ics](https://aka.ms/sk-community-calendar) file. +* Asia Pacific timezone: download the [calendar-APAC.ics](https://aka.ms/sk-community-calendar-apac) file. + +Add Semantic Kernel Development Office Hours for Python and Java to your calendar to help with development: +* Java Development Office Hours: [Java Development Office Hours](https://aka.ms/sk-java-dev-sync) +* Python Development Office Hours: [Python Development Office Hours](https://aka.ms/sk-python-dev-sync) + +If you have any questions or if you would like to showcase your project(s), please email what you'd like us to cover here: skofficehours[at]microsoft.com. + +If you are unable to make it live, all meetings will be recorded and posted online. + +## Join the conversation on Discord + +We have a growing and active channel on Discord where you can get help, engage in lively discussion, +and share what you've built with Semantic Kernel! + +Join our Discord: +[https://aka.ms/SKDiscord](https://aka.ms/SKDiscord) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..14cbb9be --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,147 @@ +# Contributing to Semantic Kernel + +You can contribute to Semantic Kernel with issues and pull requests (PRs). Simply +filing issues for problems you encounter is a great way to contribute. Contributing +code is greatly appreciated. + +## Reporting Issues + +We always welcome bug reports, API proposals and overall feedback. Here are a few +tips on how you can make reporting your issue as effective as possible. + +### Where to Report + +New issues can be reported in our [list of issues](https://github.com/microsoft/semantic-kernel/issues). + +Before filing a new issue, please search the list of issues to make sure it does +not already exist. + +If you do find an existing issue for what you wanted to report, please include +your own feedback in the discussion. Do consider upvoting (👍 reaction) the original +post, as this helps us prioritize popular issues in our backlog. + +### Writing a Good Bug Report + +Good bug reports make it easier for maintainers to verify and root cause the +underlying problem. +The better a bug report, the faster the problem will be resolved. Ideally, a bug +report should contain the following information: + +- A high-level description of the problem. +- A _minimal reproduction_, i.e. the smallest size of code/configuration required + to reproduce the wrong behavior. +- A description of the _expected behavior_, contrasted with the _actual behavior_ observed. +- Information on the environment: OS/distribution, CPU architecture, SDK version, etc. +- Additional information, e.g. Is it a regression from previous versions? Are there + any known workarounds? + +## Contributing Changes + +Project maintainers will merge accepted code changes from contributors. + +### DOs and DON'Ts + +DO's: + +- **DO** follow the standard coding conventions + + - [.NET](https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions) + - [Python](https://pypi.org/project/black/) + - [Typescript](https://typescript-eslint.io/rules/)/[React](https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules) + +- **DO** give priority to the current style of the project or file you're changing + if it diverges from the general guidelines. +- **DO** include tests when adding new features. When fixing bugs, start with + adding a test that highlights how the current behavior is broken. +- **DO** keep the discussions focused. When a new or related topic comes up + it's often better to create new issue than to side track the discussion. +- **DO** clearly state on an issue that you are going to take on implementing it. +- **DO** blog and tweet (or whatever) about your contributions, frequently! + +DON'Ts: + +- **DON'T** surprise us with big pull requests. Instead, file an issue and start + a discussion so we can agree on a direction before you invest a large amount of time. +- **DON'T** commit code that you didn't write. If you find code that you think is a good + fit to add to Semantic Kernel, file an issue and start a discussion before proceeding. +- **DON'T** submit PRs that alter licensing related files or headers. If you believe + there's a problem with them, file an issue and we'll be happy to discuss it. +- **DON'T** make new APIs without filing an issue and discussing with us first. + +### Breaking Changes + +Contributions must maintain API signature and behavioral compatibility. Contributions +that include breaking changes will be rejected. Please file an issue to discuss +your idea or change if you believe that a breaking change is warranted. + +### Suggested Workflow + +We use and recommend the following workflow: + +1. Create an issue for your work. + - You can skip this step for trivial changes. + - Reuse an existing issue on the topic, if there is one. + - Get agreement from the team and the community that your proposed change is + a good one. + - Clearly state that you are going to take on implementing it, if that's the case. + You can request that the issue be assigned to you. Note: The issue filer and + the implementer don't have to be the same person. +2. Create a personal fork of the repository on GitHub (if you don't already have one). +3. In your fork, create a branch off of main (`git checkout -b mybranch`). + - Name the branch so that it clearly communicates your intentions, such as + "issue-123" or "githubhandle-issue". +4. Make and commit your changes to your branch. +5. Add new tests corresponding to your change, if applicable. +6. Run the relevant scripts in [the section below](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#dev-scripts) to ensure that your build is clean and all tests are passing. +7. Create a PR against the repository's **main** branch. + - State in the description what issue or improvement your change is addressing. + - Verify that all the Continuous Integration checks are passing. +8. Wait for feedback or approval of your changes from the code maintainers. +9. When area owners have signed off, and all checks are green, your PR will be merged. + +### Development scripts + +The scripts below are used to build, test, and lint within the project. + +- Python: see [python/DEV_SETUP.md](https://github.com/microsoft/semantic-kernel/blob/main/python/DEV_SETUP.md#pipeline-checks). +- .NET: + - Build/Test: `run build.cmd` or `bash build.sh` + - Linting (auto-fix): `dotnet format` +- Typescript: + - Build/Test: `yarn build` + - Linting (auto-fix): `yarn lint:fix` + +### Adding Plugins and Memory Connectors + +When considering contributions to plugins and memory connectors for Semantic +Kernel, please note the following guidelines: + +#### Plugins + +We appreciate your interest in extending Semantic Kernel's functionality through +plugins. However, we want to clarify our approach to hosting plugins within our +GitHub repository. To maintain a clean and manageable codebase, we will not be +hosting plugins directly in the Semantic Kernel GitHub repository. +Instead, we encourage contributors to host their plugin code in separate +repositories under their own GitHub accounts or organization. You can then +provide a link to your plugin repository in the relevant discussions, issues, +or documentation within the Semantic Kernel repository. This approach ensures +that each plugin can be maintained independently and allows for easier tracking +of updates and issues specific to each plugin. + +#### Memory Connectors + +For memory connectors, while we won't be directly adding hosting for them within +the Semantic Kernel repository, we highly recommend building memory connectors +as separate plugins. Memory connectors play a crucial role in interfacing with +external memory systems, and treating them as plugins enhances modularity and +maintainability. + +### PR - CI Process + +The continuous integration (CI) system will automatically perform the required +builds and run tests (including the ones you are expected to run) for PRs. Builds +and test runs must be clean. + +If the CI build fails for any reason, the PR issue will be updated with a link +that can be used to determine the cause of the failure. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..9e841e7a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/java/PACKAGES.md b/PACKAGES.md similarity index 100% rename from java/PACKAGES.md rename to PACKAGES.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..715b979a --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +[![Builds](https://github.com/microsoft/semantic-kernel-java/actions/workflows/java-build.yml/badge.svg?branch=main)](https://github.com/microsoft/semantic-kernel-java/actions/workflows/java-build.yml) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.microsoft.semantic-kernel/semantickernel-api/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.microsoft.semantic-kernel/semantickernel-api) +[![License: MIT](https://img.shields.io/github/license/microsoft/semantic-kernel)](https://github.com/microsoft/semantic-kernel-java/blob/main/LICENSE) +[![Discord](https://img.shields.io/discord/1063152441819942922?label=Discord&logo=discord&logoColor=white&color=d82679)](https://aka.ms/SKDiscord) + +# Semantic Kernel for Java + +Welcome to the Semantic Kernel for Java. For detailed documentation, visit [Microsoft Learn](https://learn.microsoft.com/en-us/semantic-kernel/overview/?tabs=Java). + +[Semantic Kernel](https://learn.microsoft.com/en-us/semantic-kernel/overview/) is an SDK that integrates Large Language Models (LLMs) like [OpenAI](https://platform.openai.com/docs/introduction), [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service), and [Hugging Face](https://huggingface.co/) +with conventional programming languages like C#, Python, and Java. Semantic Kernel achieves this by allowing you to define [plugins](https://learn.microsoft.com/en-us/semantic-kernel/ai-orchestration/plugins) that can be chained together in just a [few lines of code](https://learn.microsoft.com/en-us/semantic-kernel/ai-orchestration/chaining-functions?tabs=Java#using-the-runasync-method-to-simplify-your-code). + +What makes Semantic Kernel _special_, however, is its ability to _automatically_ orchestrate plugins with AI. With Semantic Kernel [planners](https://learn.microsoft.com/en-us/semantic-kernel/ai-orchestration/planner), you can ask an LLM to generate a plan that achieves a user's unique goal. Afterwards, Semantic Kernel will execute the plan for the user. + +For C#, Python and other language support, see [microsoft/semantic-kernel](https://github.com/microsoft/semantic-kernel). + +#### Please star the repo to show your support for this project! + +![Orchestrating plugins with planner](https://learn.microsoft.com/en-us/semantic-kernel/media/kernel-infographic.png) + +## Getting started with Semantic Kernel for Java + +The quickest way to get started with the basics is to get an API key from either OpenAI or Azure OpenAI and to run one of the Java console applications/scripts below. + +1. Clone the repository: `git clone https://github.com/microsoft/semantic-kernel-java.git` +2. Follow the instructions [Start learning how to use Semantic Kernel](https://learn.microsoft.com/en-us/semantic-kernel/get-started/quick-start-guide?tabs=Java). + +## Documentation: Learning how to use Semantic Kernel + +The fastest way to learn how to use Semantic Kernel is with our walkthroughs +on our Learn site. + +1. 📖 [Overview of the kernel](https://learn.microsoft.com/en-us/semantic-kernel/ai-orchestration/?tabs=Java) +1. 🔌 [Understanding AI plugins](https://learn.microsoft.com/en-us/semantic-kernel/ai-orchestration/plugins?tabs=Java) +1. 👄 [Creating semantic functions](https://learn.microsoft.com/en-us/semantic-kernel/ai-orchestration/semantic-functions?tabs=Java) +1. 💽 [Creating native functions](https://learn.microsoft.com/en-us/semantic-kernel/ai-orchestration/native-functions?tabs=Java) +1. ⛓️ [Chaining functions together](https://learn.microsoft.com/en-us/semantic-kernel/ai-orchestration/chaining-functions?tabs=Java) +1. 🤖 [Auto create plans with planner](https://learn.microsoft.com/en-us/semantic-kernel/ai-orchestration/planner?tabs=Java) +1. 💡 [Create and run a ChatGPT plugin](https://learn.microsoft.com/en-us/semantic-kernel/ai-orchestration/chatgpt-plugins?tabs=Java) + +## Join the community + +We welcome your contributions and suggestions to SK community! One of the easiest +ways to participate is to engage in discussions in the GitHub repository. +Bug reports and fixes are welcome! + +For new features, components, or extensions, please open an issue and discuss with +us before sending a PR. This is to avoid rejection as we might be taking the core +in a different direction, but also to consider the impact on the larger ecosystem. + +To learn more and get started: + +- Read the [documentation](https://learn.microsoft.com/en-us/semantic-kernel/overview/?tabs=Java) +- Learn how to [contribute](https://learn.microsoft.com/en-us/semantic-kernel/get-started/contributing) to the project +- Join the [Discord community](https://aka.ms/SKDiscord) +- Attend [regular office hours and SK community events](COMMUNITY.md) +- Follow the team on our [blog](https://aka.ms/sk/blog) + +## Contributor Wall of Fame + +[![semantic-kernel contributors](https://contrib.rocks/image?repo=microsoft/semantic-kernel-java)](https://github.com/microsoft/semantic-kernel-java/graphs/contributors) + +## Code of Conduct + +This project has adopted the +[Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the +[Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) +with any additional questions or comments. + +## License + +Copyright (c) Microsoft Corporation. All rights reserved. + +Licensed under the [MIT](LICENSE) license. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..eed215e1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://www.microsoft.com/en-us/msrc/definition-of-a-security-vulnerability?rtc=1), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc?rtc=2). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/en-us/msrc?rtc=2). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://www.microsoft.com/en-us/msrc/bounty?rtc=2) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd?rtc=2). + + diff --git a/aiservices/google/pom.xml b/aiservices/google/pom.xml new file mode 100644 index 00000000..ef878373 --- /dev/null +++ b/aiservices/google/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + com.microsoft.semantic-kernel + semantickernel-parent + 1.2.0 + ../../pom.xml + + + semantickernel-aiservices-google + Semantic Kernel Google Services + Google services for Semantic Kernel + + + + + com.google.cloud + libraries-bom + 26.37.0 + pom + import + + + + + + + com.google.cloud + google-cloud-vertexai + + + + com.microsoft.semantic-kernel + semantickernel-api + + + + com.fasterxml.jackson.core + jackson-databind + compile + + + com.fasterxml.jackson.core + jackson-core + compile + + + + + javax.xml.stream + stax-api + provided + + + + \ No newline at end of file diff --git a/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/GeminiService.java b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/GeminiService.java new file mode 100644 index 00000000..7155074f --- /dev/null +++ b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/GeminiService.java @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.google; + +import com.google.cloud.vertexai.VertexAI; +import com.microsoft.semantickernel.services.AIService; + +import javax.annotation.Nullable; + +public class GeminiService implements AIService { + private final VertexAI client; + private final String modelId; + + protected GeminiService(VertexAI client, String modelId) { + this.client = client; + this.modelId = modelId; + } + + @Nullable + @Override + public String getModelId() { + return modelId; + } + + @Nullable + @Override + public String getServiceId() { + return null; + } + + protected VertexAI getClient() { + return client; + } +} diff --git a/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/GeminiServiceBuilder.java b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/GeminiServiceBuilder.java new file mode 100644 index 00000000..c5d3f658 --- /dev/null +++ b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/GeminiServiceBuilder.java @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.google; + +import com.google.cloud.vertexai.VertexAI; +import com.microsoft.semantickernel.builders.SemanticKernelBuilder; +import javax.annotation.Nullable; + +/** + * Builder for a Gemini service. + */ +public abstract class GeminiServiceBuilder> implements + SemanticKernelBuilder { + + @Nullable + protected String modelId; + @Nullable + protected VertexAI client; + + /** + * Sets the model ID for the service + * + * @param modelId The model ID + * @return The builder + */ + public U withModelId(String modelId) { + this.modelId = modelId; + return (U) this; + } + + /** + * Sets the VertexAI client for the service + * + * @param client The VertexAI client + * @return The builder + */ + public U withVertexAIClient(VertexAI client) { + this.client = client; + return (U) this; + } + + @Override + public abstract T build(); +} \ No newline at end of file diff --git a/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiChatCompletion.java b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiChatCompletion.java new file mode 100644 index 00000000..8aa669f0 --- /dev/null +++ b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiChatCompletion.java @@ -0,0 +1,402 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.google.chatcompletion; + +import com.google.cloud.vertexai.VertexAI; +import com.google.cloud.vertexai.api.Content; +import com.google.cloud.vertexai.api.FunctionDeclaration; +import com.google.cloud.vertexai.api.FunctionResponse; +import com.google.cloud.vertexai.api.GenerateContentResponse; +import com.google.cloud.vertexai.api.GenerationConfig; +import com.google.cloud.vertexai.api.Part; +import com.google.cloud.vertexai.api.Schema; +import com.google.cloud.vertexai.api.Tool; +import com.google.cloud.vertexai.api.Type; +import com.google.cloud.vertexai.generativeai.GenerativeModel; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.google.GeminiService; +import com.microsoft.semantickernel.aiservices.google.implementation.MonoConverter; +import com.microsoft.semantickernel.contextvariables.ContextVariableTypes; +import com.microsoft.semantickernel.exceptions.AIException; +import com.microsoft.semantickernel.exceptions.SKCheckedException; +import com.microsoft.semantickernel.exceptions.SKException; +import com.microsoft.semantickernel.orchestration.FunctionResult; +import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.orchestration.InvocationReturnMode; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.orchestration.ToolCallBehavior; +import com.microsoft.semantickernel.plugin.KernelPlugin; +import com.microsoft.semantickernel.semanticfunctions.InputVariable; +import com.microsoft.semantickernel.semanticfunctions.KernelFunction; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import com.microsoft.semantickernel.aiservices.google.GeminiServiceBuilder; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public class GeminiChatCompletion extends GeminiService implements ChatCompletionService { + + private static final Logger LOGGER = LoggerFactory.getLogger(GeminiChatCompletion.class); + + public GeminiChatCompletion(VertexAI client, String modelId) { + super(client, modelId); + } + + /** + * Create a new instance of {@link GeminiChatCompletion.Builder}. + * + * @return a new instance of {@link GeminiChatCompletion.Builder} + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public Mono>> getChatMessageContentsAsync(String prompt, + @Nullable Kernel kernel, @Nullable InvocationContext invocationContext) { + GeminiXMLPromptParser.GeminiParsedPrompt parsedPrompt = GeminiXMLPromptParser.parse(prompt); + + return this.getChatMessageContentsAsync(parsedPrompt.getChatHistory(), kernel, + invocationContext); + } + + @Override + public Mono>> getChatMessageContentsAsync(ChatHistory chatHistory, + @Nullable Kernel kernel, @Nullable InvocationContext invocationContext) { + return internalChatMessageContentsAsync( + new ChatHistory(chatHistory.getMessages()), + new ChatHistory(), + kernel, + invocationContext, + Math.min(MAXIMUM_INFLIGHT_AUTO_INVOKES, + invocationContext != null && invocationContext.getToolCallBehavior() != null + ? invocationContext.getToolCallBehavior().getMaximumAutoInvokeAttempts() + : 0)); + } + + private Mono>> internalChatMessageContentsAsync( + ChatHistory fullHistory, ChatHistory newHistory, @Nullable Kernel kernel, + @Nullable InvocationContext invocationContext, int invocationAttempts) { + + List contents = getContents(fullHistory); + + try { + GenerativeModel model = getGenerativeModel(kernel, invocationContext); + return MonoConverter.fromApiFuture(model.generateContentAsync(contents)) + .doOnError(e -> LOGGER.error("Error generating chat completion", e)) + .flatMap(result -> { + // Get ChatMessageContent from the response + GeminiChatMessageContent response = getGeminiChatMessageContentFromResponse( + result); + + // Add assistant response to the chat history + fullHistory.addMessage(response); + newHistory.addMessage(response); + + // Just return the result: + // If we don't want to attempt to invoke any functions or if we have no function calls + if (invocationAttempts <= 0 || response.getGeminiFunctionCalls().isEmpty()) { + if (invocationContext != null && invocationContext + .returnMode() == InvocationReturnMode.FULL_HISTORY) { + return Mono.just(fullHistory.getMessages()); + } + if (invocationContext != null && invocationContext + .returnMode() == InvocationReturnMode.LAST_MESSAGE_ONLY) { + ChatHistory lastMessage = new ChatHistory(); + lastMessage.addMessage(response); + + return Mono.just(lastMessage.getMessages()); + } + + return Mono.just(newHistory.getMessages()); + } + + // Perform the function calls + List> functionResults = response + .getGeminiFunctionCalls().stream() + .map(geminiFunctionCall -> performFunctionCall(kernel, invocationContext, + geminiFunctionCall)) + .collect(Collectors.toList()); + + Mono> combinedResults = Flux + .fromIterable(functionResults) + .flatMap(mono -> mono) + .collectList(); + + // Add the function responses to the chat history + return combinedResults.flatMap(results -> { + ChatMessageContent functionResponsesMessage = new GeminiChatMessageContent<>( + AuthorRole.USER, + "", null, null, null, null, results); + + fullHistory.addMessage(functionResponsesMessage); + newHistory.addMessage(functionResponsesMessage); + + return internalChatMessageContentsAsync(fullHistory, newHistory, kernel, + invocationContext, invocationAttempts - 1); + }); + }); + } catch (SKCheckedException | IOException e) { + return Mono.error(new SKException("Error generating chat completion", e)); + } + } + + // Convert from ChatHistory to List + private List getContents(ChatHistory chatHistory) { + List contents = new ArrayList<>(); + chatHistory.forEach(chatMessageContent -> { + Content.Builder contentBuilder = Content.newBuilder(); + + if (chatMessageContent.getAuthorRole() == AuthorRole.USER) { + contentBuilder.setRole(GeminiRole.USER.toString()); + + if (chatMessageContent instanceof GeminiChatMessageContent) { + GeminiChatMessageContent message = (GeminiChatMessageContent) chatMessageContent; + + message.getGeminiFunctionCalls().forEach(geminiFunction -> { + FunctionResult functionResult = geminiFunction.getFunctionResult(); + if (functionResult == null || functionResult.getResult() == null) { + throw new SKException("Gemini failed to return a result"); + } + + FunctionResponse functionResponse = FunctionResponse.newBuilder() + .setName(geminiFunction.getFunctionCall().getName()) + .setResponse(Struct.newBuilder().putFields("result", + Value.newBuilder() + .setStringValue( + (String) functionResult.getResult()) + .build())) + .build(); + + contentBuilder + .addParts(Part.newBuilder().setFunctionResponse(functionResponse)); + }); + } + } else if (chatMessageContent.getAuthorRole() == AuthorRole.ASSISTANT) { + contentBuilder.setRole(GeminiRole.MODEL.toString()); + + if (chatMessageContent instanceof GeminiChatMessageContent) { + GeminiChatMessageContent message = (GeminiChatMessageContent) chatMessageContent; + + message.getGeminiFunctionCalls().forEach(geminiFunctionCall -> { + contentBuilder.addParts(Part.newBuilder() + .setFunctionCall(geminiFunctionCall.getFunctionCall())); + }); + } + } + + if (chatMessageContent.getContent() != null + && !chatMessageContent.getContent().isEmpty()) { + contentBuilder.addParts(Part.newBuilder().setText(chatMessageContent.getContent())); + } + + contents.add(contentBuilder.build()); + }); + + return contents; + } + + private GeminiChatMessageContent getGeminiChatMessageContentFromResponse( + GenerateContentResponse response) { + StringBuilder message = new StringBuilder(); + List functionCalls = new ArrayList<>(); + + response.getCandidatesList().forEach( + candidate -> { + Content content = candidate.getContent(); + if (content.getPartsCount() == 0) { + return; + } + + content.getPartsList().forEach(part -> { + if (!part.getFunctionCall().getName().isEmpty()) { + // We only care about the function call here + // Execution of the function call will be done later + functionCalls.add(new GeminiFunctionCall(part.getFunctionCall(), null)); + } + if (!part.getText().isEmpty()) { + message.append(part.getText()); + } + }); + }); + + FunctionResultMetadata metadata = FunctionResultMetadata + .build(UUID.randomUUID().toString(), response.getUsageMetadata(), OffsetDateTime.now()); + + return new GeminiChatMessageContent<>(AuthorRole.ASSISTANT, + message.toString(), null, null, null, metadata, functionCalls); + } + + private GenerativeModel getGenerativeModel(@Nullable Kernel kernel, + @Nullable InvocationContext invocationContext) throws SKCheckedException { + GenerativeModel.Builder modelBuilder = new GenerativeModel.Builder() + .setModelName(getModelId()) + .setVertexAi(getClient()); + + if (invocationContext != null) { + if (invocationContext.getPromptExecutionSettings() != null) { + PromptExecutionSettings settings = invocationContext.getPromptExecutionSettings(); + + if (settings.getResultsPerPrompt() < 1 + || settings.getResultsPerPrompt() > MAX_RESULTS_PER_PROMPT) { + throw SKCheckedException.build("Error building generative model.", + new AIException(AIException.ErrorCodes.INVALID_REQUEST, + String.format( + "Results per prompt must be in range between 1 and %d, inclusive.", + MAX_RESULTS_PER_PROMPT))); + } + + GenerationConfig config = GenerationConfig.newBuilder() + .setMaxOutputTokens(settings.getMaxTokens()) + .setTemperature((float) settings.getTemperature()) + .setTopP((float) settings.getTopP()) + .setCandidateCount(settings.getResultsPerPrompt()) + .build(); + + modelBuilder.setGenerationConfig(config); + } + + if (invocationContext.getToolCallBehavior() != null && kernel != null) { + List tools = new ArrayList<>(); + Tool tool = getTool(kernel, invocationContext.getToolCallBehavior()); + if (tool != null) { + tools.add(tool); + } + modelBuilder.setTools(tools); + } + } + + return modelBuilder.build(); + } + + private FunctionDeclaration buildFunctionDeclaration(KernelFunction function) { + FunctionDeclaration.Builder functionBuilder = FunctionDeclaration.newBuilder(); + functionBuilder.setName( + ToolCallBehavior.formFullFunctionName(function.getPluginName(), function.getName())); + functionBuilder.setDescription(function.getDescription()); + + List parameters = function.getMetadata().getParameters(); + if (parameters != null && !parameters.isEmpty()) { + Schema.Builder parametersBuilder = Schema.newBuilder(); + + function.getMetadata().getParameters().forEach(parameter -> { + parametersBuilder.setType(Type.OBJECT); + parametersBuilder.putProperties( + parameter.getName(), + Schema.newBuilder().setType(Type.STRING) + .setDescription(parameter.getDescription()).build()); + }); + + functionBuilder.setParameters(parametersBuilder.build()); + } + + return functionBuilder.build(); + } + + @Nullable + private Tool getTool(@Nullable Kernel kernel, @Nullable ToolCallBehavior toolCallBehavior) { + if (kernel == null || toolCallBehavior == null) { + return null; + } + + Tool.Builder toolBuilder = Tool.newBuilder(); + + // If a specific function is required to be called + if (toolCallBehavior instanceof ToolCallBehavior.RequiredKernelFunction) { + KernelFunction kernelFunction = ((ToolCallBehavior.RequiredKernelFunction) toolCallBehavior) + .getRequiredFunction(); + + toolBuilder.addFunctionDeclarations(buildFunctionDeclaration(kernelFunction)); + } + // If a set of functions are enabled to be called + if (toolCallBehavior instanceof ToolCallBehavior.AllowedKernelFunctions) { + ToolCallBehavior.AllowedKernelFunctions enabledKernelFunctions = (ToolCallBehavior.AllowedKernelFunctions) toolCallBehavior; + + kernel.getPlugins() + .forEach(plugin -> plugin.getFunctions().forEach((name, function) -> { + // check if all kernel functions are enabled or if the specific function is enabled + if (enabledKernelFunctions.isAllKernelFunctionsAllowed() || + enabledKernelFunctions.isFunctionAllowed(function.getPluginName(), + function.getName())) { + toolBuilder.addFunctionDeclarations(buildFunctionDeclaration(function)); + } + })); + } + + return toolBuilder.build(); + } + + public Mono performFunctionCall(@Nullable Kernel kernel, + @Nullable InvocationContext invocationContext, GeminiFunctionCall geminiFunction) { + if (kernel == null) { + throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, + "Kernel must be provided to perform function call"); + } + + String[] name = geminiFunction.getFunctionCall().getName() + .split(ToolCallBehavior.FUNCTION_NAME_SEPARATOR); + + String pluginName = name[0]; + String functionName = name[1]; + + KernelPlugin plugin = kernel.getPlugin(pluginName); + if (plugin == null) { + throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, + String.format("Plugin %s not found in kernel", pluginName)); + } + KernelFunction function = plugin.get(functionName); + + if (function == null) { + throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, + String.format("Kernel function %s not found in plugin %s", functionName, + pluginName)); + } + + ContextVariableTypes contextVariableTypes = invocationContext == null + ? new ContextVariableTypes() + : invocationContext.getContextVariableTypes(); + + KernelFunctionArguments.Builder arguments = KernelFunctionArguments.builder(); + geminiFunction.getFunctionCall().getArgs().getFieldsMap().forEach((key, value) -> { + arguments.withVariable(key, value.getStringValue()); + }); + + return function + .invokeAsync(kernel) + .withArguments(arguments.build()) + .withResultType(contextVariableTypes.getVariableTypeForClass(String.class)) + .map(result -> new GeminiFunctionCall(geminiFunction.getFunctionCall(), result)); + } + + public static class Builder extends GeminiServiceBuilder { + + @Override + public GeminiChatCompletion build() { + if (this.client == null) { + throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, + "VertexAI client must be provided"); + } + + if (this.modelId == null || modelId.isEmpty()) { + throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, + "Gemini model id must be provided"); + } + + return new GeminiChatCompletion(client, modelId); + } + } +} diff --git a/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiChatMessageContent.java b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiChatMessageContent.java new file mode 100644 index 00000000..28cae4fb --- /dev/null +++ b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiChatMessageContent.java @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.google.chatcompletion; + +import com.google.cloud.vertexai.api.FunctionCall; +import com.google.cloud.vertexai.api.FunctionResponse; +import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Represents the content of a chat message. + * + * @param The type of the inner content. + */ +public class GeminiChatMessageContent extends ChatMessageContent { + @Nonnull + private final List geminiFunctionCalls; + + /** + * Creates a new instance of the {@link GeminiChatMessageContent} class. + * + * @param authorRole The author role that generated the content. + * @param content The content. + * @param modelId The model id. + * @param innerContent The inner content. + * @param encoding The encoding. + * @param metadata The metadata. + * @param geminiFunctionCalls The function calls. + */ + public GeminiChatMessageContent( + AuthorRole authorRole, + String content, + @Nullable String modelId, + @Nullable T innerContent, + @Nullable Charset encoding, + @Nullable FunctionResultMetadata metadata, + @Nullable List geminiFunctionCalls) { + super(authorRole, content, modelId, innerContent, encoding, metadata); + if (geminiFunctionCalls == null) { + this.geminiFunctionCalls = Collections.emptyList(); + } else { + this.geminiFunctionCalls = Collections.unmodifiableList(geminiFunctionCalls); + } + } + + /** + * Gets the function calls. + * + * @return The function calls. + */ + @Nonnull + public List getGeminiFunctionCalls() { + return Collections.unmodifiableList(geminiFunctionCalls); + } +} diff --git a/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiFunctionCall.java b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiFunctionCall.java new file mode 100644 index 00000000..fc64c830 --- /dev/null +++ b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiFunctionCall.java @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.google.chatcompletion; + +import com.google.cloud.vertexai.api.FunctionCall; +import com.microsoft.semantickernel.orchestration.FunctionResult; +import com.microsoft.semantickernel.orchestration.ToolCallBehavior; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class GeminiFunctionCall { + @Nonnull + private final FunctionCall functionCall; + @Nullable + private final FunctionResult functionResult; + private final String pluginName; + private final String functionName; + + @SuppressFBWarnings("EI_EXPOSE_REP2") + public GeminiFunctionCall( + @Nonnull FunctionCall functionCall, + @Nullable FunctionResult functionResult) { + this.functionCall = functionCall; + this.functionResult = functionResult; + + String[] name = functionCall.getName().split(ToolCallBehavior.FUNCTION_NAME_SEPARATOR); + this.pluginName = name[0]; + this.functionName = name[1]; + } + + public String getPluginName() { + return pluginName; + } + + public String getFunctionName() { + return functionName; + } + + @SuppressFBWarnings("EI_EXPOSE_REP") + public FunctionCall getFunctionCall() { + return functionCall; + } + + @Nullable + public FunctionResult getFunctionResult() { + return functionResult; + } +} diff --git a/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiRole.java b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiRole.java new file mode 100644 index 00000000..74590723 --- /dev/null +++ b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiRole.java @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.google.chatcompletion; + +public enum GeminiRole { + /** + * A user message is a message generated by the user. + */ + USER("user"), + /** + * A model message is a message generated by the model. + */ + MODEL("model"); + + private final String role; + + private GeminiRole(String role) { + this.role = role; + } + + @Override + public String toString() { + return role; + } +} diff --git a/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiXMLPromptParser.java b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiXMLPromptParser.java new file mode 100644 index 00000000..afde9a33 --- /dev/null +++ b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/chatcompletion/GeminiXMLPromptParser.java @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.google.chatcompletion; + +import com.azure.core.util.BinaryData; +import com.google.cloud.vertexai.api.FunctionDeclaration; +import com.google.cloud.vertexai.api.Schema; +import com.microsoft.semantickernel.implementation.chatcompletion.ChatPromptParseVisitor; +import com.microsoft.semantickernel.implementation.chatcompletion.ChatXMLPromptParser; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageTextContent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GeminiXMLPromptParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(GeminiXMLPromptParser.class); + + public static class GeminiParsedPrompt { + + private final ChatHistory chatHistory; + private final List functions; + + protected GeminiParsedPrompt( + ChatHistory parsedChatHistory, + @Nullable List parsedFunctions) { + this.chatHistory = parsedChatHistory; + if (parsedFunctions == null) { + parsedFunctions = new ArrayList<>(); + } + this.functions = parsedFunctions; + } + + public ChatHistory getChatHistory() { + return new ChatHistory(chatHistory.getMessages()); + } + + public List getFunctions() { + return Collections.unmodifiableList(functions); + } + } + + private static AuthorRole getAuthorRole(String role) { + switch (role) { + case "user": + return AuthorRole.USER; + case "assistant": + return AuthorRole.ASSISTANT; + case "system": + return AuthorRole.SYSTEM; + case "tool": + return AuthorRole.TOOL; + default: + LOGGER.error("Unknown role: " + role); + return AuthorRole.USER; + } + } + + private static class GeminiChatPromptParseVisitor + implements ChatPromptParseVisitor { + + @Nullable + private GeminiParsedPrompt parsedRaw = null; + private final List functionDefinitions = new ArrayList<>(); + private final ChatHistory chatHistory = new ChatHistory(); + + @Override + public ChatPromptParseVisitor addMessage( + String role, + String content) { + chatHistory.addMessage( + ChatMessageTextContent.builder() + .withContent(content) + .withAuthorRole(getAuthorRole(role)) + .build()); + return this; + } + + @Override + public ChatPromptParseVisitor addFunction( + String name, + @Nullable String description, + @Nullable BinaryData parameters) { + + // TODO: Build the parameters schema + Schema.Builder parametersBuilder = Schema.newBuilder(); + + FunctionDeclaration.Builder function = FunctionDeclaration.newBuilder() + .setName(name) + .setDescription(description) + .setParameters(parametersBuilder.build()); + + functionDefinitions.add(function.build()); + return this; + } + + @Override + public boolean areMessagesEmpty() { + return chatHistory.getMessages().isEmpty(); + } + + @Override + public ChatPromptParseVisitor fromRawPrompt( + String rawPrompt) { + + ChatMessageContent message = ChatMessageTextContent.userMessage(rawPrompt); + + this.parsedRaw = new GeminiParsedPrompt( + new ChatHistory(Collections.singletonList(message)), null); + + return this; + } + + @Override + public GeminiParsedPrompt get() { + if (parsedRaw != null) { + return parsedRaw; + } + + return new GeminiParsedPrompt(chatHistory, functionDefinitions); + } + + @Override + public ChatPromptParseVisitor reset() { + return new GeminiChatPromptParseVisitor(); + } + } + + public static GeminiParsedPrompt parse(String rawPrompt) { + ChatPromptParseVisitor visitor = ChatXMLPromptParser.parse( + rawPrompt, + new GeminiChatPromptParseVisitor()); + + return visitor.get(); + } +} diff --git a/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/implementation/MonoConverter.java b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/implementation/MonoConverter.java new file mode 100644 index 00000000..3209a03c --- /dev/null +++ b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/implementation/MonoConverter.java @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.google.implementation; + +import com.google.api.core.ApiFuture; +import reactor.core.publisher.Mono; + +public class MonoConverter { + public static Mono fromApiFuture(ApiFuture apiFuture) { + return Mono.create(sink -> { + apiFuture.addListener(() -> { + try { + T result = apiFuture.get(); + sink.success(result); + } catch (Exception e) { + sink.error(e); + } + }, runnable -> new Thread(runnable).start()); + }); + } +} diff --git a/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/textcompletion/GeminiTextGenerationService.java b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/textcompletion/GeminiTextGenerationService.java new file mode 100644 index 00000000..4d7c2a25 --- /dev/null +++ b/aiservices/google/src/main/java/com/microsoft/semantickernel/aiservices/google/textcompletion/GeminiTextGenerationService.java @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.google.textcompletion; + +import com.google.cloud.vertexai.VertexAI; +import com.google.cloud.vertexai.api.GenerateContentResponse; +import com.google.cloud.vertexai.api.GenerationConfig; +import com.google.cloud.vertexai.generativeai.GenerativeModel; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.google.GeminiService; +import com.microsoft.semantickernel.aiservices.google.implementation.MonoConverter; +import com.microsoft.semantickernel.exceptions.AIException; +import com.microsoft.semantickernel.exceptions.SKCheckedException; +import com.microsoft.semantickernel.exceptions.SKException; +import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.aiservices.google.GeminiServiceBuilder; +import com.microsoft.semantickernel.services.textcompletion.StreamingTextContent; +import com.microsoft.semantickernel.services.textcompletion.TextContent; +import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class GeminiTextGenerationService extends GeminiService implements TextGenerationService { + private static final Logger LOGGER = LoggerFactory.getLogger(GeminiTextGenerationService.class); + + public GeminiTextGenerationService(VertexAI client, String modelId) { + super(client, modelId); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public Mono> getTextContentsAsync( + String prompt, + @Nullable PromptExecutionSettings executionSettings, + @Nullable Kernel kernel) { + return this.internalGetTextAsync(prompt, executionSettings); + } + + @Override + public Flux getStreamingTextContentsAsync( + String prompt, + @Nullable PromptExecutionSettings executionSettings, + @Nullable Kernel kernel) { + return this + .internalGetTextAsync(prompt, executionSettings) + .flatMapMany(it -> Flux.fromStream(it.stream()) + .map(StreamingTextContent::new)); + } + + private Mono> internalGetTextAsync(String prompt, + @Nullable PromptExecutionSettings executionSettings) { + + try { + GenerativeModel model = getGenerativeModel(executionSettings); + return MonoConverter.fromApiFuture(model.generateContentAsync(prompt)) + .doOnError(e -> LOGGER.error("Error generating text", e)) + .flatMap(result -> { + List textContents = new ArrayList<>(); + + FunctionResultMetadata metadata = FunctionResultMetadata + .build( + UUID.randomUUID().toString(), + result.getUsageMetadata(), + OffsetDateTime.now()); + + result.getCandidatesList().forEach( + candidate -> { + candidate.getContent().getPartsList().forEach(part -> { + if (!part.getText().isEmpty()) { + textContents.add( + new TextContent(part.getText(), getModelId(), metadata)); + } + }); + }); + + return Mono.just(textContents); + }); + } catch (SKCheckedException | IOException e) { + return Mono.error(new SKException("Error generating text", e)); + } + } + + private GenerativeModel getGenerativeModel( + @Nullable PromptExecutionSettings executionSettings) throws SKCheckedException { + GenerativeModel.Builder modelBuilder = new GenerativeModel.Builder() + .setModelName(getModelId()) + .setVertexAi(getClient()); + + if (executionSettings != null) { + if (executionSettings.getResultsPerPrompt() < 1 + || executionSettings.getResultsPerPrompt() > MAX_RESULTS_PER_PROMPT) { + throw SKCheckedException.build("Error building generative model.", + new AIException(AIException.ErrorCodes.INVALID_REQUEST, + String.format( + "Results per prompt must be in range between 1 and %d, inclusive.", + MAX_RESULTS_PER_PROMPT))); + } + + GenerationConfig config = GenerationConfig.newBuilder() + .setMaxOutputTokens(executionSettings.getMaxTokens()) + .setTemperature((float) executionSettings.getTemperature()) + .setTopP((float) executionSettings.getTopP()) + .setCandidateCount(executionSettings.getResultsPerPrompt()) + .build(); + + modelBuilder.setGenerationConfig(config); + } + + return modelBuilder.build(); + } + + public static class Builder extends + GeminiServiceBuilder { + @Override + public GeminiTextGenerationService build() { + if (this.client == null) { + throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, + "VertexAI client must be provided"); + } + + if (this.modelId == null || modelId.isEmpty()) { + throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, + "Gemini model id must be provided"); + } + + return new GeminiTextGenerationService(client, modelId); + } + } +} diff --git a/aiservices/huggingface/pom.xml b/aiservices/huggingface/pom.xml new file mode 100644 index 00000000..f53983c9 --- /dev/null +++ b/aiservices/huggingface/pom.xml @@ -0,0 +1,66 @@ + + + + 4.0.0 + + + com.microsoft.semantic-kernel + semantickernel-parent + 1.2.0 + ../../pom.xml + + + semantickernel-aiservices-huggingface + Semantic Kernel Huggingface Services + Huggingface services for Semantic Kernel + + + + + com.microsoft.semantic-kernel + semantickernel-bom + ${project.version} + pom + import + + + + + + + com.microsoft.semantic-kernel + semantickernel-api + + + com.azure + azure-core + + + com.fasterxml.jackson.core + jackson-databind + compile + + + com.fasterxml.jackson.core + jackson-core + compile + + + + + javax.xml.stream + stax-api + provided + + + + + + + src/main/resources + true + + + + + diff --git a/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/HuggingFaceClient.java b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/HuggingFaceClient.java new file mode 100644 index 00000000..93fe64fd --- /dev/null +++ b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/HuggingFaceClient.java @@ -0,0 +1,178 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.huggingface; + +import com.azure.core.credential.KeyCredential; +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.semantickernel.aiservices.huggingface.models.GeneratedTextItem; +import com.microsoft.semantickernel.aiservices.huggingface.models.TextGenerationRequest; +import com.microsoft.semantickernel.exceptions.SKException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import reactor.core.publisher.Mono; +import javax.annotation.Nullable; + +public class HuggingFaceClient { + + private final KeyCredential key; + private final String endpoint; + private final HttpClient httpClient; + + public HuggingFaceClient( + KeyCredential key, + String endpoint, + HttpClient httpClient) { + this.key = key; + this.endpoint = endpoint; + this.httpClient = httpClient; + } + + /* + * TODO: TGI + * public Mono getChatMessageContentsAsync( + * String modelId, + * ChatCompletionRequest chatCompletionRequest + * ) { + * try { + * String body = new ObjectMapper().writeValueAsString(chatCompletionRequest); + * return performRequest(modelId, body) + * .handle((response, sink) -> { + * ObjectMapper mapper = new ObjectMapper(); + * JavaType type = mapper.getTypeFactory(). + * constructCollectionType(List.class, GeneratedTextItem.class); + * try { + * sink.next(mapper.readValue(response, type)); + * } catch (JsonProcessingException e) { + * sink.error( + * new SKException("Failed to deserialize response from Hugging Face", + * e)); + * } + * }); + * } catch (JsonProcessingException e) { + * return Mono.error(new SKException("Failed to serialize request body", e)); + * } + * } + * + */ + + private static class GeneratedTextItemList { + + private final List> generatedTextItems; + + @JsonCreator + public GeneratedTextItemList( + List> generatedTextItems) { + this.generatedTextItems = generatedTextItems; + } + + } + + public Mono> getTextContentsAsync( + String modelId, + TextGenerationRequest textGenerationRequest) { + try { + String body = new ObjectMapper().writeValueAsString(textGenerationRequest); + return performRequest(modelId, body) + .handle((response, sink) -> { + try { + ObjectMapper mapper = new ObjectMapper(); + JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, + GeneratedTextItemList.class); + GeneratedTextItemList data = mapper.readValue(response, + GeneratedTextItemList.class); + sink.next(data.generatedTextItems.get(0)); + } catch (Exception e) { + sink.error( + new SKException("Failed to deserialize response from Hugging Face", + e)); + } + }); + } catch (JsonProcessingException e) { + return Mono.error(new SKException("Failed to serialize request body", e)); + } + } + + private Mono performRequest(String modelId, + String body) { + HttpRequest request = new HttpRequest(HttpMethod.POST, endpoint) + .setHeader(HttpHeaderName.AUTHORIZATION, "Bearer " + key.getKey()) + .setHeader(HttpHeaderName.CONTENT_TYPE, "application/json") + .setHeader(HttpHeaderName.fromString("azureml-model-deployment"), modelId); + + request.setBody(body.getBytes(StandardCharsets.UTF_8)); + + Mono responseBody = httpClient + .send(request) + .onErrorResume( + e -> { + return Mono.error( + new SKException("Failed to send request to Hugging Face", e)); + }) + .flatMap(httpResponse -> { + if (httpResponse.getStatusCode() >= 400) { + return httpResponse.getBodyAsString() + .flatMap(errorBody -> { + return Mono.error(new SKException( + "Failed to get text content from Hugging Face. Status code: " + + httpResponse.getStatusCode() + " " + errorBody)); + }); + } else { + return Mono.just(httpResponse); + } + }) + .flatMap(HttpResponse::getBodyAsString); + return responseBody; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + @Nullable + private KeyCredential key = null; + @Nullable + private String endpoint = null; + @Nullable + private HttpClient httpClient = null; + + public HuggingFaceClient build() { + if (httpClient == null) { + httpClient = HttpClient.createDefault(); + } + if (key == null) { + throw new SKException("Key credential is required"); + } + if (endpoint == null) { + throw new SKException("Endpoint is required"); + } + return new HuggingFaceClient( + key, + endpoint, + httpClient); + } + + public Builder credential(KeyCredential key) { + this.key = key; + return this; + } + + public Builder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + + public Builder httpClient(HttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + } +} diff --git a/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/GeneratedTextItem.java b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/GeneratedTextItem.java new file mode 100644 index 00000000..6f4125c6 --- /dev/null +++ b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/GeneratedTextItem.java @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.huggingface.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class GeneratedTextItem { + + @Nullable + @JsonProperty("generated_text") + private final String generatedText; + + @Nullable + @JsonProperty("details") + private final TextGenerationDetails details; + + @JsonCreator + public GeneratedTextItem( + @JsonProperty("generated_text") @Nullable String generatedText, + @JsonProperty("details") @Nullable TextGenerationDetails details) { + this.generatedText = generatedText; + this.details = details; + } + + @Nullable + public String getGeneratedText() { + return generatedText; + } + + @Nullable + public TextGenerationDetails getDetails() { + return details; + } + + public static class TextGenerationDetails { + + @Nullable + @JsonProperty("finish_reason") + private final String finishReason; + + @JsonProperty("generated_tokens") + private final int generatedTokens; + + @Nullable + @JsonProperty("seed") + private final Long seed; + + @Nullable + @JsonProperty("prefill") + private final List prefill; + + @Nullable + @JsonProperty("tokens") + private final List tokens; + + @JsonCreator + public TextGenerationDetails( + @JsonProperty("finish_reason") @Nullable String finishReason, + @JsonProperty("generated_tokens") int generatedTokens, + @JsonProperty("seed") @Nullable Long seed, + @JsonProperty("prefill") @Nullable List prefill, + @JsonProperty("tokens") @Nullable List tokens) { + this.finishReason = finishReason; + this.generatedTokens = generatedTokens; + this.seed = seed; + if (prefill != null) { + this.prefill = new ArrayList<>(prefill); + } else { + this.prefill = null; + } + if (tokens != null) { + this.tokens = new ArrayList<>(tokens); + } else { + this.tokens = null; + } + } + + @Nullable + public String getFinishReason() { + return finishReason; + } + + public int getGeneratedTokens() { + return generatedTokens; + } + + @Nullable + public Long getSeed() { + return seed; + } + + @Nullable + public List getPrefill() { + return Collections.unmodifiableList(prefill); + } + + @Nullable + public List getTokens() { + return Collections.unmodifiableList(tokens); + } + } + + public static class TextGenerationPrefillToken { + + @JsonProperty("id") + private final int id; + + @Nullable + @JsonProperty("text") + private final String text; + + @JsonProperty("logprob") + private final double logProb; + + @JsonCreator + public TextGenerationPrefillToken( + @JsonProperty("id") int id, + @JsonProperty("text") @Nullable String text, + @JsonProperty("logprob") double logProb) { + this.id = id; + this.text = text; + this.logProb = logProb; + } + + public int getId() { + return id; + } + + @Nullable + public String getText() { + return text; + } + + public double getLogProb() { + return logProb; + } + } + + public static class TextGenerationToken extends TextGenerationPrefillToken { + + @JsonProperty("special") + private final boolean special; + + @JsonCreator + public TextGenerationToken( + @JsonProperty("special") boolean special, + @JsonProperty("id") int id, + @JsonProperty("text") @Nullable String text, + @JsonProperty("logprob") double logProb) { + super(id, text, logProb); + this.special = special; + } + + public boolean isSpecial() { + return special; + } + } +} diff --git a/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/TextGenerationRequest.java b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/TextGenerationRequest.java new file mode 100644 index 00000000..fd0b6077 --- /dev/null +++ b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/TextGenerationRequest.java @@ -0,0 +1,260 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.huggingface.models; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.microsoft.semantickernel.aiservices.huggingface.services.HuggingFacePromptExecutionSettings; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; + +@JsonInclude(Include.NON_NULL) +public class TextGenerationRequest { + + /// + /// The input string to generate text for. + /// + @Nullable + @JsonProperty("inputs") + private final List inputs; + + /// + /// Enable streaming + /// + @JsonProperty("stream") + private final boolean stream; + + /// + /// Parameters used by the model for generation. + /// + + @Nullable + @JsonProperty("parameters") + private final HuggingFaceTextParameters parameters; + + /// + /// Options used by the model for generation. + /// + @Nullable + @JsonProperty("options") + private final HuggingFaceTextOptions options; + + public TextGenerationRequest( + @Nullable String inputs, + boolean stream, + @Nullable HuggingFaceTextParameters parameters, + @Nullable HuggingFaceTextOptions options) { + this.inputs = Arrays.asList(inputs); + this.stream = stream; + this.parameters = parameters; + this.options = options; + } + + public static TextGenerationRequest fromPromptAndExecutionSettings(String prompt, + HuggingFacePromptExecutionSettings executionSettings) { + return new TextGenerationRequest( + prompt, + false, + new HuggingFaceTextParameters( + executionSettings.getTopK(), + executionSettings.getTopP(), + executionSettings.getTemperature(), + executionSettings.getRepetitionPenalty(), + executionSettings.getMaxTokens(), + executionSettings.getMaxTime(), + true, + null, + null, + executionSettings.getDetails()), + new HuggingFaceTextOptions()); + } + + public static class HuggingFaceTextParameters { + + /// + /// (Default: None). Number to define the top tokens considered within the sample operation to create new text. + /// + @Nullable + @JsonProperty("top_k") + private final Integer topK; + + /// + /// (Default: None). Define the tokens that are within the sample operation of text generation. + /// Add tokens in the sample for more probable to least probable until the sum of the probabilities + /// is greater than top_p. + /// + @Nullable + @JsonProperty("top_p") + private final Double topP; + + /// + /// (Default: 1.0). Range (0.0-100.0). The temperature of the sampling operation. + /// 1 means regular sampling, 0 means always take the highest score, + /// 100.0 is getting closer to uniform probability. + /// + @Nullable + @JsonProperty("temperature") + private final Double temperature; + + /// + /// (Default: None). (0.0-100.0). The more a token is used within generation + /// the more it is penalized to not be picked in successive generation passes. + /// + @Nullable + @JsonProperty("repetition_penalty") + private final Double repetitionPenalty; + + /// + /// (Default: None). Range (0-250). The amount of new tokens to be generated, + /// this does not include the input length it is a estimate of the size of generated text you want. + /// Each new tokens slows down the request, so look for balance between response times + /// and length of text generated. + /// + @Nullable + @JsonProperty("max_new_tokens") + private final Integer maxNewTokens; + + /// + /// (Default: None). Range (0-120.0). The amount of time in seconds that the query should take maximum. + /// Network can cause some overhead so it will be a soft limit. + /// Use that in combination with max_new_tokens for best results. + /// + @Nullable + @JsonProperty("max_time") + private final Double maxTime; + + /// + /// (Default: True). If set to False, the return results will not contain the original query making it easier for prompting. + /// + @JsonProperty("return_full_text") + private final boolean returnFullText; + + /// + /// (Default: 1). The number of proposition you want to be returned. + /// + @Nullable + @JsonProperty("num_return_sequences") + private final Integer numReturnSequences; + + /// + /// (Optional: True). Whether or not to use sampling, use greedy decoding otherwise. + /// + @Nullable + @JsonProperty("do_sample") + private final Boolean doSample; + + /// + /// (Optional: True) Whether or not to include the details of the generation. + /// + /// + /// Disabling this won't provide information about token usage. + /// + @Nullable + @JsonProperty("details") + private final Boolean details; + + public HuggingFaceTextParameters( + @JsonProperty("top_k") @Nullable Integer topK, + @JsonProperty("top_p") @Nullable Double topP, + @JsonProperty("temperature") @Nullable Double temperature, + @JsonProperty("repetition_penalty") @Nullable Double repetitionPenalty, + @JsonProperty("max_new_tokens") @Nullable Integer maxNewTokens, + @JsonProperty("max_time") @Nullable Double maxTime, + @JsonProperty("return_full_text") boolean returnFullText, + @JsonProperty("num_return_sequences") @Nullable Integer numReturnSequences, + @JsonProperty("do_sample") @Nullable Boolean doSample, + @JsonProperty("details") @Nullable Boolean details) { + this.topK = topK; + this.topP = topP; + this.temperature = temperature; + this.repetitionPenalty = repetitionPenalty; + this.maxNewTokens = maxNewTokens; + this.maxTime = maxTime; + this.returnFullText = returnFullText; + this.numReturnSequences = numReturnSequences; + this.doSample = doSample; + this.details = details; + } + + @Nullable + public Integer getTopK() { + return topK; + } + + @Nullable + public Double getTopP() { + return topP; + } + + @Nullable + public Double getTemperature() { + return temperature; + } + + @Nullable + public Double getRepetitionPenalty() { + return repetitionPenalty; + } + + @Nullable + public Integer getMaxNewTokens() { + return maxNewTokens; + } + + @Nullable + public Double getMaxTime() { + return maxTime; + } + + public boolean isReturnFullText() { + return returnFullText; + } + + @Nullable + public Integer getNumReturnSequences() { + return numReturnSequences; + } + + @Nullable + public Boolean getDoSample() { + return doSample; + } + + @Nullable + public Boolean getDetails() { + return details; + } + } + + @SuppressFBWarnings("SS_SHOULD_BE_STATIC") + public static class HuggingFaceTextOptions { + + /// + /// (Default: true). There is a cache layer on the inference API to speedup requests we have already seen. + /// Most models can use those results as is as models are deterministic (meaning the results will be the same anyway). + /// However if you use a non deterministic model, you can set this parameter to prevent the caching mechanism from being + /// used resulting in a real new query. + /// + @JsonProperty("use_cache") + private final boolean useCache = true; + + /// + /// (Default: false) If the model is not ready, wait for it instead of receiving 503. + /// It limits the number of requests required to get your inference done. + /// It is advised to only set this flag to true after receiving a 503 error as it will limit hanging in your application to known places. + /// + @JsonProperty("wait_for_model") + private final boolean waitForModel = false; + + public boolean isUseCache() { + return useCache; + } + + public boolean isWaitForModel() { + return waitForModel; + } + } +} diff --git a/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/chat/ChatCompletionRequest.java.ignore b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/chat/ChatCompletionRequest.java.ignore new file mode 100644 index 00000000..99972819 --- /dev/null +++ b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/chat/ChatCompletionRequest.java.ignore @@ -0,0 +1,201 @@ +package com.microsoft.semantickernel.aiservices.huggingface.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.Nullable; + +// TODO Support TGI +public class ChatCompletionRequest { + + + /// + /// This is the default name when using TGI and will be ignored as the TGI will only target the current activated model. + /// + public static final String TextGenerationInferenceDefaultModel = "tgi"; + + + /// + /// Model name to use for generation. + /// + /// + /// When using TGI this parameter will be ignored. + /// + @Nullable + public final String model; + + /// + /// Indicates whether to get the response as stream or not. + /// + public final boolean stream; + + @Nullable + public final List messages; + + /// + /// Whether to return log probabilities of the output tokens or not. If true, returns the log probabilities of each + /// output token returned in the content of message. + /// + @Nullable + public final Boolean logprobs; + + /// + /// An integer between 0 and 5 specifying the number of most likely tokens to return at each token position, each with + /// an associated log probability. logprobs must be set to true if this parameter is used. + /// + @Nullable + public final Integer topLogProbs; + + /// + /// The maximum number of tokens that can be generated in the chat completion. + /// + @Nullable + public final Integer maxTokens; + + /// + /// Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, + /// increasing the model's likelihood to talk about new topics + /// + + @Nullable + public final Float presencePenalty; + /// + /// Up to 4 sequences where the API will stop generating further tokens. + /// + @Nullable + public final List stop; + + /// + /// The seed to use for generating a similar output. + /// + @Nullable + public final Long seed; + + /// + /// What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while + /// lower values like 0.2 will make it more focused and deterministic. + /// + /// We generally recommend altering this or `top_p` but not both. + /// + @Nullable + public final Float temperature; + + /// + /// An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the + /// tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. + /// + @Nullable + public final Float topP; + + public ChatCompletionRequest( + @JsonProperty("model") String model, + @JsonProperty("stream") boolean stream, + @JsonProperty("messages") List messages, + @JsonProperty("logprobs") Boolean logprobs, + + @Nullable + @JsonProperty("top_logprobs") Integer topLogProbs, + + @Nullable + @JsonProperty("max_tokens") Integer maxTokens, + + @Nullable + @JsonProperty("presence_penalty") Float presencePenalty, + @Nullable + @JsonProperty("stop") List stop, + + @Nullable + @JsonProperty("seed") Long seed, + @Nullable + @JsonProperty("temperature") Float temperature, + @Nullable + @JsonProperty("top_p") + Float topP + ) { + + this.model = model; + this.stream = stream; + this.messages = messages; + this.logprobs = logprobs; + this.topLogProbs = topLogProbs; + this.maxTokens = maxTokens; + this.presencePenalty = presencePenalty; + this.stop = stop; + this.seed = seed; + this.temperature = temperature; + this.topP = topP; + } + + public static class ChatMessageToolCall { + + @Nullable + private final String id; + + @Nullable + private final String type; + + private final ChatMessageFunction function; + + public ChatMessageToolCall( + @Nullable + @JsonProperty("id") String id, + @Nullable + @JsonProperty("type") String type, + @Nullable + @JsonProperty("function") ChatMessageFunction function + ) { + this.id = id; + this.type = type; + this.function = function; + } + } + + public static class ChatMessageFunction { + + @Nullable + public final String description; + @Nullable + public final String name; + @Nullable + public final String parameters; + + public ChatMessageFunction( + @JsonProperty("description") String description, + @JsonProperty("name") String name, + @JsonProperty("parameters") String parameters + ) { + this.description = description; + this.name = name; + this.parameters = parameters; + } + } + + public static class ChatMessage { + + @Nullable + public final String role; + @Nullable + public final String content; + @Nullable + public final String name; + @Nullable + public final List toolCalls; + + + public ChatMessage( + @Nullable + @JsonProperty("role") String role, + @Nullable + @JsonProperty("content") String content, + @Nullable + @JsonProperty("name") String name, + @Nullable + @JsonProperty("tool_calls") List toolCalls + ) { + this.role = role; + this.content = content; + this.name = name; + this.toolCalls = toolCalls; + } + + } +} diff --git a/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/chat/HuggingFaceChatCompletionService.java.ignore b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/chat/HuggingFaceChatCompletionService.java.ignore new file mode 100644 index 00000000..f6f05a9e --- /dev/null +++ b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/chat/HuggingFaceChatCompletionService.java.ignore @@ -0,0 +1,181 @@ +package com.microsoft.semantickernel.aiservices.huggingface.services; + +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.huggingface.HuggingFaceClient; +import com.microsoft.semantickernel.aiservices.huggingface.models.ChatCompletionRequest; +import com.microsoft.semantickernel.aiservices.huggingface.models.HuggingFaceXMLPromptParser; +import com.microsoft.semantickernel.aiservices.huggingface.models.HuggingFaceXMLPromptParser.HuggingFaceParsedPrompt; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import reactor.core.publisher.Mono; + +// TODO Support TGI +public class HuggingFaceChatCompletionService implements ChatCompletionService { + + private final String modelId; + private final String serviceId; + private final HuggingFaceClient client; + + public HuggingFaceChatCompletionService( + String modelId, + String serviceId, + HuggingFaceClient client) { + this.modelId = modelId; + this.serviceId = serviceId; + this.client = client; + } + + public Mono>> getChatMessageContentsAsync( + ChatHistory chatHistory, + @Nullable Kernel kernel, + @Nullable HuggingFacePromptExecutionSettings executionSettings) { + + String model = modelId; + if (executionSettings.getModelId() != null && !executionSettings.getModelId().isEmpty()) { + model = executionSettings.getModelId(); + } + + ChatCompletionRequest request = new ChatCompletionRequest( + model, + false, + chatHistory + .getMessages() + .stream() + .map( + message -> { + return new ChatCompletionRequest.ChatMessage( + message.getAuthorRole().name(), + message.getContent(), + null, + null + ); + } + ) + .collect(Collectors.toList()), + executionSettings.getLogprobs(), + executionSettings.getTopLogProbs(), + executionSettings.getMaxTokens(), + new Float(executionSettings.getPresencePenalty()), + executionSettings.getStopSequences(), + executionSettings.getSeed(), + new Float(executionSettings.getTemperature()), + new Float(executionSettings.getTopP()) + ); + + return client + .getChatMessageContentsAsync(modelId, request) + .map(result -> { + return Collections.singletonList(new ChatMessageContent<>( + AuthorRole.SYSTEM, + result) + ); + }); + } + + @Override + public Mono>> getChatMessageContentsAsync( + ChatHistory chatHistory, + @Nullable Kernel kernel, + @Nullable InvocationContext invocationContext) { + + HuggingFacePromptExecutionSettings executionSettings; + if (invocationContext != null && invocationContext.getPromptExecutionSettings() != null) { + executionSettings = HuggingFacePromptExecutionSettings.fromExecutionSettings( + invocationContext.getPromptExecutionSettings()); + } else { + executionSettings = new HuggingFacePromptExecutionSettings( + PromptExecutionSettings.builder().build()); + } + + return getChatMessageContentsAsync(chatHistory, kernel, executionSettings); + + } + + @Override + public Mono>> getChatMessageContentsAsync( + String prompt, + @Nullable Kernel kernel, + @Nullable InvocationContext invocationContext) { + HuggingFaceParsedPrompt parsed = HuggingFaceXMLPromptParser.parse(prompt); + + ChatHistory history = new ChatHistory(); + parsed.getChatRequestMessages() + .forEach(message -> { + history.addMessage(AuthorRole.valueOf(message.role.toUpperCase(Locale.ROOT)), + message.content); + }); + + return getChatMessageContentsAsync(history, kernel, invocationContext); + + } + + @Nullable + @Override + public String getModelId() { + return modelId; + } + + @Nullable + @Override + public String getServiceId() { + return serviceId; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + @Nullable + private String modelId; + @Nullable + private HuggingFaceClient client; + @Nullable + private String serviceId; + + /** + * Sets the model ID for the service + * + * @param modelId The model ID + * @return The builder + */ + public Builder withModelId(String modelId) { + this.modelId = modelId; + return this; + } + + /** + * Sets the service ID for the service + * + * @param serviceId The service ID + * @return The builder + */ + public Builder withServiceId(String serviceId) { + this.serviceId = serviceId; + return this; + } + + public Builder withHuggingFaceClient(HuggingFaceClient client) { + this.client = client; + return this; + } + + public ChatCompletionService build() { + return new HuggingFaceChatCompletionService( + this.modelId, + this.serviceId, + this.client); + } + } + +} diff --git a/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/chat/HuggingFaceXMLPromptParser.java.ignore b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/chat/HuggingFaceXMLPromptParser.java.ignore new file mode 100644 index 00000000..6e647485 --- /dev/null +++ b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/models/chat/HuggingFaceXMLPromptParser.java.ignore @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.huggingface.models; + +import com.azure.core.util.BinaryData; +import com.microsoft.semantickernel.aiservices.huggingface.models.ChatCompletionRequest.ChatMessage; +import com.microsoft.semantickernel.aiservices.huggingface.models.ChatCompletionRequest.ChatMessageFunction; +import com.microsoft.semantickernel.aiservices.huggingface.models.ChatCompletionRequest.ChatMessageToolCall; +import com.microsoft.semantickernel.services.chatcompletion.ChatPromptParseVisitor; +import com.microsoft.semantickernel.services.chatcompletion.ChatXMLPromptParser; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO Support TGI +public class HuggingFaceXMLPromptParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(HuggingFaceXMLPromptParser.class); + + public static class HuggingFaceParsedPrompt { + + private final List chatRequestMessages; + private final List functions; + + protected HuggingFaceParsedPrompt( + List parsedMessages, + @Nullable List parsedFunctions + ) { + this.chatRequestMessages = parsedMessages; + if (parsedFunctions == null) { + parsedFunctions = new ArrayList<>(); + } + this.functions = parsedFunctions; + } + + public List getChatRequestMessages() { + return chatRequestMessages; + } + + public List getFunctions() { + return functions; + } + } + + private static class HuggingFaceChatPromptParseVisitor implements + ChatPromptParseVisitor { + + private HuggingFaceParsedPrompt parsedRaw; + private final List functionDefinitions = new ArrayList<>(); + private final List messages = new ArrayList<>(); + + @Override + public ChatPromptParseVisitor addMessage( + String role, + String content) { + messages.add(new ChatMessage( + role, + content, + null, + null)); + return this; + } + + @Override + public ChatPromptParseVisitor addFunction( + String name, + @Nullable + String description, + @Nullable + BinaryData parameters) { + + String paramString = null; + if (parameters != null) { + paramString = parameters.toString(); + } + + ChatMessageToolCall function = new ChatMessageToolCall( + name, + null, + new ChatMessageFunction( + description, + name, + paramString + ) + ); + + functionDefinitions.add(function); + + return this; + } + + @Override + public boolean areMessagesEmpty() { + return messages.isEmpty(); + } + + @Override + public ChatPromptParseVisitor fromRawPrompt( + String rawPrompt) { + + ChatMessage message = new ChatMessage( + "user", + rawPrompt, + null, + null + ); + + this.parsedRaw = new HuggingFaceParsedPrompt(Collections.singletonList(message), + null); + + return this; + } + + @Override + public HuggingFaceParsedPrompt get() { + if (parsedRaw != null) { + return parsedRaw; + } + + return new HuggingFaceParsedPrompt(messages, functionDefinitions); + } + + @Override + public ChatPromptParseVisitor reset() { + return new HuggingFaceChatPromptParseVisitor(); + } + } + + public static HuggingFaceParsedPrompt parse(String rawPrompt) { + ChatPromptParseVisitor visitor = ChatXMLPromptParser.parse( + rawPrompt, + new HuggingFaceChatPromptParseVisitor()); + + return visitor.get(); + } +} \ No newline at end of file diff --git a/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/services/HuggingFacePromptExecutionSettings.java b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/services/HuggingFacePromptExecutionSettings.java new file mode 100644 index 00000000..ed3a4df5 --- /dev/null +++ b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/services/HuggingFacePromptExecutionSettings.java @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.huggingface.services; + +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nullable; + +public class HuggingFacePromptExecutionSettings extends PromptExecutionSettings { + + @Nullable + private final Integer topK; + @Nullable + private final Double repetitionPenalty; + @Nullable + private final Double maxTime; + @Nullable + private final Boolean details; + @Nullable + private final Boolean logProbs; + @Nullable + private final Integer topLogProbs; + @Nullable + private final Long seed; + + public HuggingFacePromptExecutionSettings(PromptExecutionSettings copy) { + super( + copy.getServiceId(), + copy.getModelId(), + copy.getTemperature(), + copy.getTopP(), + copy.getPresencePenalty(), + copy.getFrequencyPenalty(), + copy.getMaxTokens(), + copy.getResultsPerPrompt(), + copy.getBestOf(), + copy.getUser(), + copy.getStopSequences(), + copy.getTokenSelectionBiases(), + copy.getResponseFormat() == null ? null : copy.getResponseFormat().toString()); + this.topK = null; + this.repetitionPenalty = null; + this.maxTime = null; + this.details = null; + this.logProbs = null; + this.topLogProbs = null; + this.seed = null; + } + + /** + * Create a new instance of PromptExecutionSettings. + * + * @param serviceId The id of the AI service to use for prompt execution. + * @param modelId The id of the model to use for prompt execution. + * @param temperature The temperature setting for prompt execution. + * @param topP The topP setting for prompt execution. + * @param presencePenalty The presence penalty setting for prompt execution. + * @param frequencyPenalty The frequency penalty setting for prompt execution. + * @param maxTokens The maximum number of tokens to generate in the output. + * @param resultsPerPrompt The number of results to generate for each prompt. + * @param bestOf The best of setting for prompt execution. + * @param user The user to associate with the prompt execution. + * @param stopSequences The stop sequences to use for prompt execution. + * @param tokenSelectionBiases The token selection biases to use for prompt execution. + * @param responseFormat The response format to use for prompt execution + */ + public HuggingFacePromptExecutionSettings( + String serviceId, + String modelId, + Double temperature, + Double topP, + Double presencePenalty, + Double frequencyPenalty, + Integer maxTokens, + Integer resultsPerPrompt, + Integer bestOf, + String user, + @Nullable List stopSequences, + @Nullable Map tokenSelectionBiases, + @Nullable String responseFormat, + @Nullable Integer topK, + @Nullable Double repetitionPenalty, + @Nullable Double maxTime, + @Nullable Boolean details, + @Nullable Boolean logProbs, + @Nullable Integer topLogProbs, + @Nullable Long seed) { + super( + serviceId, modelId, temperature, topP, presencePenalty, frequencyPenalty, maxTokens, + resultsPerPrompt, bestOf, user, stopSequences, tokenSelectionBiases, responseFormat); + + this.topK = topK; + this.repetitionPenalty = repetitionPenalty; + this.maxTime = maxTime; + this.details = details; + this.logProbs = logProbs; + this.topLogProbs = topLogProbs; + this.seed = seed; + } + + public static HuggingFacePromptExecutionSettings fromExecutionSettings( + PromptExecutionSettings promptExecutionSettings) { + if (promptExecutionSettings instanceof HuggingFacePromptExecutionSettings) { + return (HuggingFacePromptExecutionSettings) promptExecutionSettings; + } + + return new HuggingFacePromptExecutionSettings( + promptExecutionSettings.getServiceId(), + promptExecutionSettings.getModelId(), + promptExecutionSettings.getTemperature(), + promptExecutionSettings.getTopP(), + promptExecutionSettings.getPresencePenalty(), + promptExecutionSettings.getFrequencyPenalty(), + promptExecutionSettings.getMaxTokens(), + promptExecutionSettings.getResultsPerPrompt(), + promptExecutionSettings.getBestOf(), + promptExecutionSettings.getUser(), + promptExecutionSettings.getStopSequences(), + promptExecutionSettings.getTokenSelectionBiases(), + promptExecutionSettings.getResponseFormat() != null + ? promptExecutionSettings.getResponseFormat().toString() + : null, + null, + null, + null, + null, + null, + null, + null); + } + + @Nullable + public Integer getTopK() { + return topK; + } + + @Nullable + public Double getRepetitionPenalty() { + return repetitionPenalty; + } + + @Nullable + public Double getMaxTime() { + return maxTime; + } + + @Nullable + public Boolean getDetails() { + return details; + } + + @Nullable + public Boolean getLogprobs() { + return logProbs; + } + + @Nullable + public Integer getTopLogProbs() { + return topLogProbs; + } + + @Nullable + public Long getSeed() { + return seed; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !(o instanceof HuggingFacePromptExecutionSettings)) { + return false; + } + if (!super.equals(o)) { + return false; + } + HuggingFacePromptExecutionSettings that = (HuggingFacePromptExecutionSettings) o; + return Objects.equals(topK, that.topK) && + Objects.equals(repetitionPenalty, that.repetitionPenalty) && + Objects.equals(maxTime, that.maxTime) && + Objects.equals(details, that.details) && + Objects.equals(logProbs, that.logProbs) && + Objects.equals(topLogProbs, that.topLogProbs) && + Objects.equals(seed, that.seed); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), topK, repetitionPenalty, maxTime, details, logProbs, + topLogProbs, seed); + } +} diff --git a/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/services/HuggingFaceTextGenerationService.java b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/services/HuggingFaceTextGenerationService.java new file mode 100644 index 00000000..96e05ff9 --- /dev/null +++ b/aiservices/huggingface/src/main/java/com/microsoft/semantickernel/aiservices/huggingface/services/HuggingFaceTextGenerationService.java @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.huggingface.services; + +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.huggingface.HuggingFaceClient; +import com.microsoft.semantickernel.aiservices.huggingface.models.TextGenerationRequest; +import com.microsoft.semantickernel.aiservices.huggingface.models.TextGenerationRequest.HuggingFaceTextOptions; +import com.microsoft.semantickernel.aiservices.huggingface.models.TextGenerationRequest.HuggingFaceTextParameters; +import com.microsoft.semantickernel.exceptions.SKException; +import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.services.textcompletion.StreamingTextContent; +import com.microsoft.semantickernel.services.textcompletion.TextContent; +import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public class HuggingFaceTextGenerationService implements TextGenerationService { + + private final String modelId; + private final String serviceId; + private final HuggingFaceClient client; + + public HuggingFaceTextGenerationService( + String modelId, + String serviceId, + HuggingFaceClient client) { + this.modelId = modelId; + this.serviceId = serviceId; + this.client = client; + } + + public Mono> getTextContentsAsync( + String prompt, + @Nullable HuggingFacePromptExecutionSettings huggingFacePromptExecutionSettings, + @Nullable Kernel kernel) { + + HuggingFaceTextParameters textParameters = getHuggingFaceTextParameters( + huggingFacePromptExecutionSettings); + + TextGenerationRequest textGenerationRequest = new TextGenerationRequest( + prompt, + false, + textParameters, + new HuggingFaceTextOptions()); + + return client + .getTextContentsAsync(modelId, textGenerationRequest) + .map(result -> result + .stream() + .map(item -> new TextContent( + item.getGeneratedText() != null ? item.getGeneratedText() : "", + modelId, + FunctionResultMetadata.build(UUID.randomUUID().toString()))) + .collect(Collectors.toList())); + } + + @Override + public Mono> getTextContentsAsync( + String prompt, + @Nullable PromptExecutionSettings executionSettings, + @Nullable Kernel kernel) { + + HuggingFacePromptExecutionSettings huggingFacePromptExecutionSettings = null; + + if (executionSettings != null) { + huggingFacePromptExecutionSettings = HuggingFacePromptExecutionSettings + .fromExecutionSettings( + executionSettings); + } + + return getTextContentsAsync( + prompt, + huggingFacePromptExecutionSettings, + kernel); + + } + + @Override + public Flux getStreamingTextContentsAsync(String prompt, + @Nullable PromptExecutionSettings executionSettings, @Nullable Kernel kernel) { + throw new SKException("Streaming text content is not supported"); + } + + private static @Nullable HuggingFaceTextParameters getHuggingFaceTextParameters( + @Nullable HuggingFacePromptExecutionSettings executionSettings) { + HuggingFaceTextParameters textParameters = null; + if (executionSettings != null) { + textParameters = new HuggingFaceTextParameters( + executionSettings.getTopK(), + executionSettings.getTopP(), + executionSettings.getTemperature(), + executionSettings.getRepetitionPenalty(), + executionSettings.getMaxTokens(), + executionSettings.getMaxTime(), + true, + executionSettings.getResultsPerPrompt(), + null, + executionSettings.getDetails()); + } + return textParameters; + } + + @Nullable + @Override + public String getModelId() { + return modelId; + } + + @Nullable + @Override + public String getServiceId() { + return serviceId; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + @Nullable + protected String modelId; + @Nullable + protected HuggingFaceClient client; + @Nullable + protected String serviceId; + + /** + * Sets the model ID for the service + * + * @param modelId The model ID + * @return The builder + */ + public Builder withModelId(String modelId) { + this.modelId = modelId; + return this; + } + + /** + * Sets the service ID for the service + * + * @param serviceId The service ID + * @return The builder + */ + public Builder withServiceId(String serviceId) { + this.serviceId = serviceId; + return this; + } + + public Builder withHuggingFaceClient(HuggingFaceClient client) { + this.client = client; + return this; + } + + public HuggingFaceTextGenerationService build() { + + if (this.modelId == null) { + throw new SKException( + "Model ID is required to build HuggingFaceTextGenerationService"); + } + + if (this.serviceId == null) { + throw new SKException( + "Service ID is required to build HuggingFaceTextGenerationService"); + } + + if (this.client == null) { + throw new SKException( + "HuggingFaceClient is required to build HuggingFaceTextGenerationService"); + } + + return new HuggingFaceTextGenerationService( + this.modelId, + this.serviceId, + this.client); + } + } +} diff --git a/java/aiservices/openai/pom.xml b/aiservices/openai/pom.xml similarity index 76% rename from java/aiservices/openai/pom.xml rename to aiservices/openai/pom.xml index 1ffd9f24..a7b12c09 100644 --- a/java/aiservices/openai/pom.xml +++ b/aiservices/openai/pom.xml @@ -6,7 +6,7 @@ com.microsoft.semantic-kernel semantickernel-parent - 1.1.5 + 1.2.0 ../../pom.xml @@ -40,6 +40,21 @@ stax-api provided + + io.opentelemetry + opentelemetry-sdk + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.mockito + mockito-core + test + diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/OpenAiService.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/OpenAiService.java similarity index 100% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/OpenAiService.java rename to aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/OpenAiService.java diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/audio/OpenAiAudioToTextService.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/audio/OpenAiAudioToTextService.java similarity index 100% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/audio/OpenAiAudioToTextService.java rename to aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/audio/OpenAiAudioToTextService.java diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/audio/OpenAiTextToAudioService.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/audio/OpenAiTextToAudioService.java similarity index 100% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/audio/OpenAiTextToAudioService.java rename to aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/audio/OpenAiTextToAudioService.java diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java similarity index 89% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java rename to aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java index e30351a7..6bdb4f1c 100644 --- a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java +++ b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java @@ -11,12 +11,17 @@ import com.azure.ai.openai.models.ChatCompletionsTextResponseFormat; import com.azure.ai.openai.models.ChatCompletionsToolCall; import com.azure.ai.openai.models.ChatCompletionsToolDefinition; +import com.azure.ai.openai.models.ChatMessageImageContentItem; +import com.azure.ai.openai.models.ChatMessageImageDetailLevel; +import com.azure.ai.openai.models.ChatMessageImageUrl; import com.azure.ai.openai.models.ChatRequestAssistantMessage; +import com.azure.ai.openai.models.ChatRequestFunctionMessage; import com.azure.ai.openai.models.ChatRequestMessage; import com.azure.ai.openai.models.ChatRequestSystemMessage; import com.azure.ai.openai.models.ChatRequestToolMessage; import com.azure.ai.openai.models.ChatRequestUserMessage; import com.azure.ai.openai.models.ChatResponseMessage; +import com.azure.ai.openai.models.CompletionsUsage; import com.azure.ai.openai.models.FunctionCall; import com.azure.core.util.BinaryData; import com.fasterxml.jackson.core.JsonProcessingException; @@ -38,6 +43,7 @@ import com.microsoft.semantickernel.hooks.PreChatCompletionEvent; import com.microsoft.semantickernel.hooks.PreToolCallEvent; import com.microsoft.semantickernel.implementation.CollectionUtil; +import com.microsoft.semantickernel.implementation.telemetry.SemanticKernelTelemetry; import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; import com.microsoft.semantickernel.orchestration.InvocationContext; @@ -50,6 +56,10 @@ import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageContentType; +import com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageImageContent; +import com.microsoft.semantickernel.services.openai.OpenAiServiceBuilder; +import io.opentelemetry.api.trace.Span; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -124,7 +134,7 @@ public Mono>> getChatMessageContentsAsync( .returnMode() == InvocationReturnMode.LAST_MESSAGE_ONLY) { chatHistoryResult = new ChatHistory( Collections.singletonList( - CollectionUtil.getLastOrNull(chatHistory.getMessages()))); + CollectionUtil.getLastOrNull(chatHistoryResult.getMessages()))); } return Mono.just(chatHistoryResult.getMessages()); @@ -139,7 +149,7 @@ public Mono>> getChatMessageContentsAsync( String prompt, @Nullable Kernel kernel, @Nullable InvocationContext invocationContext) { - ParsedPrompt parsedPrompt = XMLPromptParser.parse(prompt); + ParsedPrompt parsedPrompt = OpenAiXMLPromptParser.parse(prompt); ChatMessages messages = new ChatMessages(parsedPrompt.getChatRequestMessages()); @@ -218,6 +228,54 @@ public ChatMessages addChatMessage(List chatMessageCon newMessages, tmpChatMessageContent); } + + /** + * Checks that the two messages have a similar history + * + * @param messages The messages to merge in + * @return The merged chat messages + */ + boolean assertCommonHistory(List messages) { + int index = 0; + while (index < messages.size() && index < this.allMessages.size()) { + ChatRequestMessage a = messages.get(index); + ChatRequestMessage b = this.allMessages.get(index); + + boolean matches = false; + if (a instanceof ChatRequestAssistantMessage + && b instanceof ChatRequestAssistantMessage) { + matches = Objects.equals(((ChatRequestAssistantMessage) a).getContent(), + ((ChatRequestAssistantMessage) b).getContent()); + } else if (a instanceof ChatRequestSystemMessage + && b instanceof ChatRequestSystemMessage) { + matches = Objects.equals(((ChatRequestSystemMessage) a).getContent(), + ((ChatRequestSystemMessage) b).getContent()); + } else if (a instanceof ChatRequestUserMessage + && b instanceof ChatRequestUserMessage) { + matches = Objects.equals(((ChatRequestUserMessage) a).getContent(), + ((ChatRequestUserMessage) b).getContent()); + } else if (a instanceof ChatRequestFunctionMessage + && b instanceof ChatRequestFunctionMessage) { + matches = Objects.equals(((ChatRequestFunctionMessage) a).getContent(), + ((ChatRequestFunctionMessage) b).getContent()); + } else if (a instanceof ChatRequestToolMessage + && b instanceof ChatRequestToolMessage) { + matches = Objects.equals(((ChatRequestToolMessage) a).getContent(), + ((ChatRequestToolMessage) b).getContent()); + } + + if (!matches) { + LOGGER.warn("Messages do not match at index: " + index + + " you might be merging unrelated message histories"); + return false; + } + + index++; + } + + return true; + + } } private Mono internalChatMessageContentsAsync( @@ -261,17 +319,28 @@ private Mono internalChatMessageContentsAsync( invocationContext))) .getOptions(); + Span span = SemanticKernelTelemetry.startChatCompletionSpan( + getModelId(), + SemanticKernelTelemetry.OPEN_AI_PROVIDER, + options.getMaxTokens(), + options.getTemperature(), + options.getTopP()); return getClient() .getChatCompletionsWithResponse(getDeploymentName(), options, OpenAIRequestSettings.getRequestOptions()) .flatMap(completionsResult -> { if (completionsResult.getStatusCode() >= 400) { + SemanticKernelTelemetry.endSpanWithError(span); return Mono.error(new AIException(ErrorCodes.SERVICE_ERROR, "Request failed: " + completionsResult.getStatusCode())); } + SemanticKernelTelemetry.endSpanWithUsage(span, + completionsResult.getValue().getUsage()); + return Mono.just(completionsResult.getValue()); }) .flatMap(completions -> { + List responseMessages = completions .getChoices() .stream() @@ -334,7 +403,10 @@ private Mono internalChatMessageContentsAsync( if (autoInvokeAttempts > 0) { ChatMessages currentMessages = messages; if (e instanceof FunctionInvocationError) { - currentMessages = currentMessages.addAll( + currentMessages.assertCommonHistory( + ((FunctionInvocationError) e).getMessages()); + + currentMessages = new ChatMessages( ((FunctionInvocationError) e).getMessages()); } return internalChatMessageContentsAsync( @@ -510,7 +582,7 @@ private OpenAIFunctionToolCall extractOpenAIFunctionToolCall( private Mono> getChatMessageContentsAsync( ChatCompletions completions) { - FunctionResultMetadata completionMetadata = FunctionResultMetadata.build( + FunctionResultMetadata completionMetadata = FunctionResultMetadata.build( completions.getId(), completions.getUsage(), completions.getCreatedAt()); @@ -621,11 +693,8 @@ private List getToolCalls( throw SKException.build("Failed to parse tool arguments", e); } } else { - return new OpenAIFunctionToolCall( - call.getId(), - null, - null, - null); + throw new SKException( + "Unknown tool call type: " + call.getClass().getSimpleName()); } }) .collect(Collectors.toList()); @@ -671,7 +740,7 @@ private static ChatCompletionsOptions getCompletionsOptions( chatRequestMessages = chatRequestMessages .stream() - .map(XMLPromptParser::unescapeRequest) + .map(OpenAiXMLPromptParser::unescapeRequest) .collect(Collectors.toList()); ChatCompletionsOptions options = new ChatCompletionsOptions(chatRequestMessages) @@ -869,6 +938,10 @@ private static ChatRequestMessage getChatRequestMessage( AuthorRole authorRole = message.getAuthorRole(); String content = message.getContent(); + if (message.getContentType() == ChatMessageContentType.IMAGE_URL && content != null) { + return formImageMessage(message, content); + } + switch (authorRole) { case ASSISTANT: return formAssistantMessage(message, content); @@ -892,7 +965,19 @@ private static ChatRequestMessage getChatRequestMessage( LOGGER.debug("Unexpected author role: {}", authorRole); throw new SKException("Unexpected author role: " + authorRole); } + } + + private static ChatRequestUserMessage formImageMessage(ChatMessageContent message, + String content) { + ChatMessageImageUrl imageUrl = new ChatMessageImageUrl(content); + if (message instanceof ChatMessageImageContent) { + ChatMessageImageDetailLevel detail = ChatMessageImageDetailLevel.fromString( + ((ChatMessageImageContent) message).getDetail().toString()); + imageUrl.setDetail(detail); + } + return new ChatRequestUserMessage( + Collections.singletonList(new ChatMessageImageContentItem(imageUrl))); } private static ChatRequestAssistantMessage formAssistantMessage( @@ -959,7 +1044,7 @@ static ChatRequestMessage getChatRequestMessage( /** * Builder for creating a new instance of {@link OpenAIChatCompletion}. */ - public static class Builder extends ChatCompletionService.Builder { + public static class Builder extends OpenAiServiceBuilder { @Override public OpenAIChatCompletion build() { diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatMessageContent.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatMessageContent.java similarity index 100% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatMessageContent.java rename to aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatMessageContent.java diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatResponse.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatResponse.java similarity index 100% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatResponse.java rename to aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatResponse.java diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunction.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunction.java similarity index 100% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunction.java rename to aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunction.java diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunctionToolCall.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunctionToolCall.java similarity index 100% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunctionToolCall.java rename to aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIFunctionToolCall.java diff --git a/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAiXMLPromptParser.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAiXMLPromptParser.java new file mode 100644 index 00000000..4088f72f --- /dev/null +++ b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAiXMLPromptParser.java @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.openai.chatcompletion; + +import com.azure.ai.openai.models.ChatRequestAssistantMessage; +import com.azure.ai.openai.models.ChatRequestFunctionMessage; +import com.azure.ai.openai.models.ChatRequestMessage; +import com.azure.ai.openai.models.ChatRequestSystemMessage; +import com.azure.ai.openai.models.ChatRequestToolMessage; +import com.azure.ai.openai.models.ChatRequestUserMessage; +import com.azure.ai.openai.models.FunctionDefinition; +import com.azure.core.util.BinaryData; +import com.microsoft.semantickernel.exceptions.SKException; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.implementation.chatcompletion.ChatPromptParseVisitor; +import com.microsoft.semantickernel.implementation.chatcompletion.ChatXMLPromptParser; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.UUID; +import javax.annotation.Nullable; +import org.apache.commons.text.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class OpenAiXMLPromptParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(OpenAiXMLPromptParser.class); + + private static class OpenAiChatPromptParseVisitor implements + ChatPromptParseVisitor { + + @Nullable + private ParsedPrompt parsedRaw = null; + private final List functionDefinitions = new ArrayList<>(); + private final List messages = new ArrayList<>(); + + @Override + public ChatPromptParseVisitor addMessage(String role, + String content) { + messages.add(getChatRequestMessage(role, content)); + return this; + } + + @Override + public ChatPromptParseVisitor addFunction( + String name, + @Nullable String description, + @Nullable BinaryData parameters) { + FunctionDefinition function = new FunctionDefinition(name); + + if (description != null) { + function.setDescription(description); + } + + if (parameters != null) { + function.setParameters(parameters); + } + + functionDefinitions.add(function); + + return this; + } + + @Override + public boolean areMessagesEmpty() { + return messages.isEmpty(); + } + + @Override + public ChatPromptParseVisitor fromRawPrompt(String rawPrompt) { + ChatRequestUserMessage message = new ChatRequestUserMessage(rawPrompt); + + if (message.getName() == null) { + message.setName(UUID.randomUUID().toString()); + } + + this.parsedRaw = new ParsedPrompt(Collections.singletonList(message), null); + return this; + } + + @Override + public ParsedPrompt get() { + if (parsedRaw != null) { + return parsedRaw; + } + + return new ParsedPrompt(messages, functionDefinitions); + } + + @Override + public ChatPromptParseVisitor reset() { + return new OpenAiChatPromptParseVisitor(); + } + } + + public static ParsedPrompt parse(String rawPrompt) { + ChatPromptParseVisitor visitor = ChatXMLPromptParser.parse(rawPrompt, + new OpenAiChatPromptParseVisitor()); + + return visitor.get(); + + } + + private static ChatRequestMessage getChatRequestMessage( + String role, + String content) { + try { + AuthorRole authorRole = AuthorRole.valueOf(role.toUpperCase(Locale.ROOT)); + return OpenAIChatCompletion.getChatRequestMessage(authorRole, content); + } catch (IllegalArgumentException e) { + LOGGER.debug("Unknown author role: " + role); + throw new SKException("Unknown author role: " + role); + } + } + + public static ChatRequestMessage unescapeRequest(ChatRequestMessage message) { + if (message instanceof ChatRequestUserMessage) { + ChatRequestUserMessage chatRequestMessage = (ChatRequestUserMessage) message; + String content = StringEscapeUtils.unescapeXml( + chatRequestMessage.getContent().toString()); + + return new ChatRequestUserMessage(content) + .setName(chatRequestMessage.getName()); + } else if (message instanceof ChatRequestSystemMessage) { + ChatRequestSystemMessage chatRequestMessage = (ChatRequestSystemMessage) message; + String content = StringEscapeUtils.unescapeXml(chatRequestMessage.getContent()); + + return new ChatRequestSystemMessage(content) + .setName(chatRequestMessage.getName()); + } else if (message instanceof ChatRequestAssistantMessage) { + ChatRequestAssistantMessage chatRequestMessage = (ChatRequestAssistantMessage) message; + String content = StringEscapeUtils.unescapeXml(chatRequestMessage.getContent()); + + return new ChatRequestAssistantMessage(content) + .setToolCalls(chatRequestMessage.getToolCalls()) + .setFunctionCall(chatRequestMessage.getFunctionCall()) + .setName(chatRequestMessage.getName()); + } else if (message instanceof ChatRequestFunctionMessage) { + ChatRequestFunctionMessage chatRequestMessage = (ChatRequestFunctionMessage) message; + String content = StringEscapeUtils.unescapeXml(chatRequestMessage.getContent()); + + return new ChatRequestFunctionMessage( + chatRequestMessage.getName(), + content); + } else if (message instanceof ChatRequestToolMessage) { + ChatRequestToolMessage chatRequestMessage = (ChatRequestToolMessage) message; + String content = StringEscapeUtils.unescapeXml(chatRequestMessage.getContent()); + + return new ChatRequestToolMessage( + content, + chatRequestMessage.getToolCallId()); + } + + throw new SKException("Unknown message type: " + message.getClass().getSimpleName()); + } +} \ No newline at end of file diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/ParsedPrompt.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/ParsedPrompt.java similarity index 100% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/ParsedPrompt.java rename to aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/ParsedPrompt.java diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/implementation/OpenAIRequestSettings.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/implementation/OpenAIRequestSettings.java similarity index 77% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/implementation/OpenAIRequestSettings.java rename to aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/implementation/OpenAIRequestSettings.java index eefceaf1..8da85ed9 100644 --- a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/implementation/OpenAIRequestSettings.java +++ b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/implementation/OpenAIRequestSettings.java @@ -2,7 +2,9 @@ package com.microsoft.semantickernel.aiservices.openai.implementation; import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.policy.UserAgentPolicy; import com.azure.core.http.rest.RequestOptions; +import com.azure.core.util.Context; import java.io.IOException; import java.io.InputStream; import java.util.Properties; @@ -18,15 +20,19 @@ public final class OpenAIRequestSettings { private static final String SEMANTIC_KERNEL_VERSION_PROPERTY_NAME = "semantic-kernel.version"; private static final String SEMANTIC_KERNEL_VERSION_PROPERTIES_FILE = "semantic-kernel-version.properties"; - private static final String version; + private static final String useragent; + + private static final String header; static { - version = loadVersion(); + String version = loadVersion(); + useragent = "semantic-kernel-java/" + version; + header = "java/" + version; } private static String loadVersion() { - String version = "Java/unknown"; + String version = "unknown"; try (InputStream settingsFile = OpenAIRequestSettings.class.getResourceAsStream( SEMANTIC_KERNEL_VERSION_PROPERTIES_FILE)) { @@ -36,7 +42,7 @@ private static String loadVersion() { if (props.containsKey(SEMANTIC_KERNEL_VERSION_PROPERTY_NAME)) { String skVersion = props.getProperty(SEMANTIC_KERNEL_VERSION_PROPERTY_NAME); if (skVersion != null && !skVersion.isEmpty()) { - return "Java/" + skVersion; + return skVersion; } } } catch (IOException e) { @@ -53,6 +59,8 @@ private static String loadVersion() { */ public static RequestOptions getRequestOptions() { return new RequestOptions() - .setHeader(HttpHeaderName.fromString("Semantic-Kernel-Version"), version); + .setHeader(HttpHeaderName.fromString("Semantic-Kernel-Version"), header) + .setContext( + new Context(UserAgentPolicy.APPEND_USER_AGENT_CONTEXT_KEY, useragent)); } } diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java similarity index 90% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java rename to aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java index 15179d81..57e3dd1f 100644 --- a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java +++ b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java @@ -3,6 +3,7 @@ import com.azure.ai.openai.OpenAIAsyncClient; import com.azure.ai.openai.models.CompletionsOptions; +import com.azure.ai.openai.models.CompletionsUsage; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.aiservices.openai.OpenAiService; import com.microsoft.semantickernel.aiservices.openai.implementation.OpenAIRequestSettings; @@ -13,6 +14,8 @@ import com.microsoft.semantickernel.services.textcompletion.StreamingTextContent; import com.microsoft.semantickernel.services.textcompletion.TextContent; import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; +import com.microsoft.semantickernel.implementation.telemetry.SemanticKernelTelemetry; +import io.opentelemetry.api.trace.Span; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -80,18 +83,27 @@ protected Mono> internalCompleteTextAsync( CompletionsOptions completionsOptions = getCompletionsOptions(text, requestSettings); + Span span = SemanticKernelTelemetry.startTextCompletionSpan( + getModelId(), + SemanticKernelTelemetry.OPEN_AI_PROVIDER, + completionsOptions.getMaxTokens(), + completionsOptions.getTemperature(), + completionsOptions.getTopP()); return getClient() .getCompletionsWithResponse(getDeploymentName(), completionsOptions, OpenAIRequestSettings.getRequestOptions()) .flatMap(completionsResult -> { if (completionsResult.getStatusCode() >= 400) { + SemanticKernelTelemetry.endSpanWithError(span); return Mono.error(new AIException(ErrorCodes.SERVICE_ERROR, "Request failed: " + completionsResult.getStatusCode())); } + SemanticKernelTelemetry.endSpanWithUsage(span, + completionsResult.getValue().getUsage()); return Mono.just(completionsResult.getValue()); }) .map(completions -> { - FunctionResultMetadata metadata = FunctionResultMetadata.build( + FunctionResultMetadata metadata = FunctionResultMetadata.build( completions.getId(), completions.getUsage(), completions.getCreatedAt()); diff --git a/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textembedding/OpenAITextEmbeddingGenerationService.java b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textembedding/OpenAITextEmbeddingGenerationService.java new file mode 100644 index 00000000..a46540c0 --- /dev/null +++ b/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textembedding/OpenAITextEmbeddingGenerationService.java @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.openai.textembedding; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.models.EmbeddingItem; +import com.azure.ai.openai.models.Embeddings; +import com.azure.ai.openai.models.EmbeddingsOptions; +import com.microsoft.semantickernel.aiservices.openai.OpenAiService; +import com.microsoft.semantickernel.exceptions.AIException; +import com.microsoft.semantickernel.services.openai.OpenAiServiceBuilder; +import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; +import com.microsoft.semantickernel.services.textembedding.Embedding; +import com.microsoft.semantickernel.services.textembedding.TextEmbeddingGenerationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +/** + * An OpenAI implementation of a {@link TextEmbeddingGenerationService}. + * + */ +public class OpenAITextEmbeddingGenerationService extends OpenAiService + implements TextEmbeddingGenerationService { + private static final Logger LOGGER = LoggerFactory + .getLogger(OpenAITextEmbeddingGenerationService.class); + private static final int DEFAULT_DIMENSIONS = 1536; + private final int dimensions; + + /** + * Creates a new {@link OpenAITextEmbeddingGenerationService}. + * + * @param client OpenAI client + * @param deploymentName deployment name + * @param modelId OpenAI model id + * @param serviceId Service id + */ + public OpenAITextEmbeddingGenerationService( + OpenAIAsyncClient client, + String deploymentName, + String modelId, + @Nullable String serviceId, + int dimensions) { + super(client, serviceId, modelId, deploymentName); + this.dimensions = dimensions; + } + + /** + * Creates a builder for creating a {@link OpenAITextEmbeddingGenerationService}. + * + * @return A new {@link OpenAITextEmbeddingGenerationService} builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Generates embeddings for the given data. + * + * @param data The data to generate embeddings for. + * @return A Mono that completes with the embeddings. + */ + @Override + public Mono> generateEmbeddingsAsync(List data) { + return this.internalGenerateTextEmbeddingsAsync(data); + } + + protected Mono> internalGenerateTextEmbeddingsAsync(List data) { + EmbeddingsOptions options = new EmbeddingsOptions(data) + .setModel(getModelId()) + .setDimensions(dimensions) + .setInputType("string"); + + return getClient() + .getEmbeddings(getModelId(), options) + .flatMapIterable(Embeddings::getData) + .mapNotNull(EmbeddingItem::getEmbedding) + .map(ArrayList::new) + .mapNotNull(Embedding::new) + .collectList(); + } + + /** + * A builder for creating a {@link OpenAITextEmbeddingGenerationService}. + */ + public static class Builder extends + OpenAiServiceBuilder { + private int dimensions = DEFAULT_DIMENSIONS; + + /** + * Sets the dimensions for the embeddings. + * + * @param dimensions The dimensions for the embeddings. + * @return The builder. + */ + public Builder withDimensions(int dimensions) { + this.dimensions = dimensions; + return this; + } + + @Override + public OpenAITextEmbeddingGenerationService build() { + if (this.client == null) { + throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, + "OpenAI client must be provided"); + } + + if (this.modelId == null || modelId.isEmpty()) { + throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, + "OpenAI model id must be provided"); + } + + if (deploymentName == null) { + LOGGER.debug("Deployment name is not provided, using model id as deployment name"); + deploymentName = modelId; + } + + return new OpenAITextEmbeddingGenerationService(client, deploymentName, modelId, + serviceId, dimensions); + } + } +} diff --git a/java/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.audio.AudioToTextService$Builder b/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.audio.AudioToTextService$Builder similarity index 100% rename from java/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.audio.AudioToTextService$Builder rename to aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.audio.AudioToTextService$Builder diff --git a/java/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.audio.TextToAudioService$Builder b/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.audio.TextToAudioService$Builder similarity index 100% rename from java/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.audio.TextToAudioService$Builder rename to aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.audio.TextToAudioService$Builder diff --git a/java/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService$Builder b/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService$Builder similarity index 100% rename from java/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService$Builder rename to aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService$Builder diff --git a/java/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.textcompletion.TextGenerationService$Builder b/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.textcompletion.TextGenerationService$Builder similarity index 100% rename from java/aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.textcompletion.TextGenerationService$Builder rename to aiservices/openai/src/main/resources/META-INF/services/com.microsoft.semantickernel.services.textcompletion.TextGenerationService$Builder diff --git a/java/aiservices/openai/src/main/resources/com/microsoft/semantickernel/aiservices/openai/implementation/semantic-kernel-version.properties b/aiservices/openai/src/main/resources/com/microsoft/semantickernel/aiservices/openai/implementation/semantic-kernel-version.properties similarity index 100% rename from java/aiservices/openai/src/main/resources/com/microsoft/semantickernel/aiservices/openai/implementation/semantic-kernel-version.properties rename to aiservices/openai/src/main/resources/com/microsoft/semantickernel/aiservices/openai/implementation/semantic-kernel-version.properties diff --git a/aiservices/openai/src/test/java/com/microsoft/semantickernel/aiservices/openai/OtelCaptureTest.java b/aiservices/openai/src/test/java/com/microsoft/semantickernel/aiservices/openai/OtelCaptureTest.java new file mode 100644 index 00000000..c8bde155 --- /dev/null +++ b/aiservices/openai/src/test/java/com/microsoft/semantickernel/aiservices/openai/OtelCaptureTest.java @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.aiservices.openai; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.models.ChatCompletions; +import com.azure.ai.openai.models.ChatCompletionsOptions; +import com.azure.ai.openai.models.Completions; +import com.azure.ai.openai.models.CompletionsOptions; +import com.azure.ai.openai.models.CompletionsUsage; +import com.azure.core.http.rest.Response; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; +import com.microsoft.semantickernel.aiservices.openai.textcompletion.OpenAITextGenerationService; +import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.util.ArrayList; +import java.util.Collection; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import reactor.core.publisher.Mono; + +public class OtelCaptureTest { + + private static OpenTelemetrySdk otel; + private static ArrayList spans = new ArrayList<>(); + + @BeforeEach + public void clearSpans() { + spans.clear(); + } + + @BeforeAll + public static void setup() { + + SdkTracerProvider tracerProvider = SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.builder(new SpanExporter() { + @Override + public CompletableResultCode export(Collection collection) { + spans.addAll(collection); + return new CompletableResultCode(); + } + + @Override + public CompletableResultCode flush() { + return new CompletableResultCode(); + } + + @Override + public CompletableResultCode shutdown() { + return new CompletableResultCode(); + } + }) + .build()) + .build(); + + GlobalOpenTelemetry.resetForTest(); + + otel = OpenTelemetrySdk.builder() + .setTracerProvider(tracerProvider) + .buildAndRegisterGlobal(); + } + + @AfterAll + public static void shutdown() { + otel.shutdown(); + } + + @Test + public void otelTextCaptureTest() { + + OpenAIAsyncClient openAIAsyncClient = Mockito.mock(OpenAIAsyncClient.class); + + CompletionsUsage completionsUsage = Mockito.mock(CompletionsUsage.class); + Mockito.when(completionsUsage.getCompletionTokens()).thenReturn(22); + Mockito.when(completionsUsage.getPromptTokens()).thenReturn(55); + + Completions completions = Mockito.mock(Completions.class); + Mockito.when(completions.getUsage()).thenReturn(completionsUsage); + + Response response = Mockito.mock(Response.class); + Mockito.when(response.getStatusCode()).thenReturn(200); + Mockito.when(response.getValue()).thenReturn(completions); + + Mockito.when(openAIAsyncClient.getCompletionsWithResponse( + Mockito.any(), + Mockito.any(), + Mockito.any())).thenAnswer(invocation -> Mono.just(response)); + + TextGenerationService client = OpenAITextGenerationService.builder() + .withOpenAIAsyncClient(openAIAsyncClient) + .withModelId("a-model") + .build(); + + try { + client.getTextContentsAsync( + "foo", + null, + null).block(); + } catch (Exception e) { + // Expect to fail + } + + Assertions.assertFalse(spans.isEmpty()); + Assertions.assertEquals("a-model", + spans.get(0).getAttributes().get(AttributeKey.stringKey("gen_ai.request.model"))); + Assertions.assertEquals("text.completions", + spans.get(0).getAttributes().get(AttributeKey.stringKey("gen_ai.operation.name"))); + Assertions.assertEquals("openai", + spans.get(0).getAttributes().get(AttributeKey.stringKey("gen_ai.system"))); + Assertions.assertEquals(22, + spans.get(0).getAttributes() + .get(AttributeKey.longKey("gen_ai.response.completion_tokens"))); + Assertions.assertEquals(55, + spans.get(0).getAttributes() + .get(AttributeKey.longKey("gen_ai.response.prompt_tokens"))); + + } + + @Test + public void otelChatCaptureTest() { + OpenAIAsyncClient openAIAsyncClient = Mockito.mock(OpenAIAsyncClient.class); + + CompletionsUsage completionsUsage = Mockito.mock(CompletionsUsage.class); + Mockito.when(completionsUsage.getCompletionTokens()).thenReturn(21); + Mockito.when(completionsUsage.getPromptTokens()).thenReturn(42); + + ChatCompletions chatCompletions = Mockito.mock(ChatCompletions.class); + Mockito.when(chatCompletions.getUsage()).thenReturn(completionsUsage); + + Response response = Mockito.mock(Response.class); + Mockito.when(response.getStatusCode()).thenReturn(200); + Mockito.when(response.getValue()).thenReturn(chatCompletions); + + Mockito.when(openAIAsyncClient.getChatCompletionsWithResponse( + Mockito.any(), + Mockito.any(), + Mockito.any())).thenAnswer(invocation -> Mono.just(response)); + + OpenAIChatCompletion client = OpenAIChatCompletion.builder() + .withOpenAIAsyncClient(openAIAsyncClient) + .withModelId("a-model") + .build(); + + try { + client.getChatMessageContentsAsync( + "foo", + null, + null).block(); + } catch (Exception e) { + // Expect to fail + } + + Assertions.assertFalse(spans.isEmpty()); + Assertions.assertEquals("a-model", + spans.get(0).getAttributes().get(AttributeKey.stringKey("gen_ai.request.model"))); + Assertions.assertEquals("chat.completions", + spans.get(0).getAttributes().get(AttributeKey.stringKey("gen_ai.operation.name"))); + Assertions.assertEquals("openai", + spans.get(0).getAttributes().get(AttributeKey.stringKey("gen_ai.system"))); + Assertions.assertEquals(21, + spans.get(0).getAttributes() + .get(AttributeKey.longKey("gen_ai.response.completion_tokens"))); + Assertions.assertEquals(42, + spans.get(0).getAttributes() + .get(AttributeKey.longKey("gen_ai.response.prompt_tokens"))); + } +} diff --git a/java/aiservices/openai/src/test/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAiChatCompletionTest.java b/aiservices/openai/src/test/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAiChatCompletionTest.java similarity index 99% rename from java/aiservices/openai/src/test/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAiChatCompletionTest.java rename to aiservices/openai/src/test/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAiChatCompletionTest.java index 21593219..5a25e1ae 100644 --- a/java/aiservices/openai/src/test/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAiChatCompletionTest.java +++ b/aiservices/openai/src/test/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAiChatCompletionTest.java @@ -107,7 +107,6 @@ public ChatCompletions getValue() { return new ObjectMapper() .readValue(String.format(message, "Snuggles"), ChatCompletions.class); - } catch (Exception e) { throw new RuntimeException(e); } diff --git a/java/aiservices/openai/src/test/resources/com/microsoft/semantickernel/aiservices/openai/chatcompletion/chatCompletion.txt b/aiservices/openai/src/test/resources/com/microsoft/semantickernel/aiservices/openai/chatcompletion/chatCompletion.txt similarity index 100% rename from java/aiservices/openai/src/test/resources/com/microsoft/semantickernel/aiservices/openai/chatcompletion/chatCompletion.txt rename to aiservices/openai/src/test/resources/com/microsoft/semantickernel/aiservices/openai/chatcompletion/chatCompletion.txt diff --git a/java/api-test/integration-tests/pom.xml b/api-test/integration-tests/pom.xml similarity index 93% rename from java/api-test/integration-tests/pom.xml rename to api-test/integration-tests/pom.xml index 5ab14e89..48f5b608 100644 --- a/java/api-test/integration-tests/pom.xml +++ b/api-test/integration-tests/pom.xml @@ -6,7 +6,7 @@ com.microsoft.semantic-kernel api-test - 1.1.5 + 1.2.0 ../pom.xml @@ -56,6 +56,11 @@ semantickernel-api test + + com.microsoft.semantic-kernel + semantickernel-experimental + test + org.xerial @@ -84,13 +89,18 @@ mysql test + + com.redis + testcontainers-redis + 2.2.2 + test + org.wiremock wiremock 3.3.1 test - @@ -174,7 +184,7 @@ foo https://localhost:8443/ - ../../../ + ../../ diff --git a/java/api-test/integration-tests/scripts/generateCert.sh b/api-test/integration-tests/scripts/generateCert.sh similarity index 100% rename from java/api-test/integration-tests/scripts/generateCert.sh rename to api-test/integration-tests/scripts/generateCert.sh diff --git a/java/api-test/integration-tests/scripts/recordMappings.sh b/api-test/integration-tests/scripts/recordMappings.sh similarity index 100% rename from java/api-test/integration-tests/scripts/recordMappings.sh rename to api-test/integration-tests/scripts/recordMappings.sh diff --git a/java/api-test/integration-tests/scripts/runWiremocks.sh b/api-test/integration-tests/scripts/runWiremocks.sh similarity index 100% rename from java/api-test/integration-tests/scripts/runWiremocks.sh rename to api-test/integration-tests/scripts/runWiremocks.sh diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example01NativeFunctionsTest.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example01NativeFunctionsTest.java similarity index 100% rename from java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example01NativeFunctionsTest.java rename to api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example01NativeFunctionsTest.java diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example03_ArgumentsTest.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example03_ArgumentsTest.java similarity index 100% rename from java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example03_ArgumentsTest.java rename to api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example03_ArgumentsTest.java diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example05_InlineFunctionDefinitionTest.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example05_InlineFunctionDefinitionTest.java similarity index 87% rename from java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example05_InlineFunctionDefinitionTest.java rename to api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example05_InlineFunctionDefinitionTest.java index 135a927f..f74268b2 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example05_InlineFunctionDefinitionTest.java +++ b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/Example05_InlineFunctionDefinitionTest.java @@ -6,6 +6,7 @@ import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import com.microsoft.semantickernel.semanticfunctions.KernelFunction; @@ -27,13 +28,13 @@ public void main(WireMockRuntimeInfo wmRuntimeInfo) { .endpoint("http://localhost:" + wmRuntimeInfo.getHttpPort()) .buildAsyncClient(); - TextGenerationService textGenerationService = TextGenerationService.builder() + OpenAIChatCompletion chatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) - .withModelId("text-davinci-003") + .withModelId("gpt-35-turbo") .build(); Kernel kernel = Kernel.builder() - .withAIService(TextGenerationService.class, textGenerationService) + .withAIService(OpenAIChatCompletion.class, chatCompletion) .build(); System.out.println("======== Inline Function Definition ========"); @@ -63,7 +64,7 @@ public void main(WireMockRuntimeInfo wmRuntimeInfo) { .build()) .build(); - WireMockUtil.mockCompletionResponse("I missed the F1 final race", "a-response"); + WireMockUtil.mockChatCompletionResponse("I missed the F1 final race", "a-response"); var result = kernel.invokeAsync(excuseFunction) .withArguments( @@ -74,7 +75,7 @@ public void main(WireMockRuntimeInfo wmRuntimeInfo) { Assertions.assertEquals("a-response", result.getResult()); - WireMockUtil.mockCompletionResponse("sorry I forgot your birthday", "a-response-2"); + WireMockUtil.mockChatCompletionResponse("sorry I forgot your birthday", "a-response-2"); result = kernel.invokeAsync(excuseFunction) .withArguments( @@ -85,7 +86,7 @@ public void main(WireMockRuntimeInfo wmRuntimeInfo) { Assertions.assertEquals("a-response-2", result.getResult()); - WireMockUtil.mockCompletionResponse("Translate this date ", "a-response-3"); + WireMockUtil.mockChatCompletionResponse("Translate this date ", "a-response-3"); var date = DateTimeFormatter.ISO_LOCAL_DATE.withZone(ZoneOffset.UTC) .format(Instant.ofEpochSecond(1)); diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/ImportingMultiplePluginsTest.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/ImportingMultiplePluginsTest.java similarity index 100% rename from java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/ImportingMultiplePluginsTest.java rename to api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/ImportingMultiplePluginsTest.java diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/KernelHooksTest.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/KernelHooksTest.java similarity index 98% rename from java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/KernelHooksTest.java rename to api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/KernelHooksTest.java index d53db894..16b9cada 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/KernelHooksTest.java +++ b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/KernelHooksTest.java @@ -29,7 +29,7 @@ private static Builder getKernelBuilder(WireMockRuntimeInfo wmRuntimeInfo) { .buildAsyncClient(); ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() - .withModelId("text-davinci-003") + .withModelId("gpt-35-turbo") .withOpenAIAsyncClient(client) .build(); diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/RenderingTest.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/RenderingTest.java similarity index 94% rename from java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/RenderingTest.java rename to api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/RenderingTest.java index 319940da..39b11182 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/RenderingTest.java +++ b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/RenderingTest.java @@ -210,12 +210,12 @@ public List WithListReturn() { @DefineKernelFunction(name = "WithListReturn2", returnType = "java.util.List") public List WithListReturn2() { - return List.of(1, 2, 3); + return List.of(4, 5, 6); } @DefineKernelFunction(name = "WithListReturn3", returnType = "java.util.List") public Mono> WithListReturn3() { - return Mono.just(List.of(1, 2, 3)); + return Mono.just(List.of(7, 8, 9)); } @DefineKernelFunction(name = "WithListReturn4", returnType = "java.util.List") @@ -239,15 +239,11 @@ public void canHandleIterableReturnFromFunction() { .build()) .block(); - Assertions.assertTrue( - wm.getAllServeEvents().get(0).getRequest().getBodyAsString().equals( - "{\"messages\":[" - + "{\"role\":\"user\",\"content\":\"[]\"}," - + "{\"role\":\"user\",\"content\":\"[1, 2, 3]\"}," - + "{\"role\":\"user\",\"content\":\"[1, 2, 3]\"}," - + "{\"role\":\"user\",\"content\":\"[1, 2, 3]\"}," - + "{\"role\":\"user\",\"content\":\"[]\"}" - + "],\"model\":\"gpt-35-turbo-2\"}")); + String requestBody = wm.getAllServeEvents().get(0).getRequest().getBodyAsString(); + Assertions.assertTrue(requestBody.contains("{\"role\":\"user\",\"content\":\"[]")); + Assertions.assertTrue(requestBody.contains("{\"role\":\"user\",\"content\":\"[1, 2, 3]")); + Assertions.assertTrue(requestBody.contains("{\"role\":\"user\",\"content\":\"[4, 5, 6]")); + Assertions.assertTrue(requestBody.contains("{\"role\":\"user\",\"content\":\"[7, 8, 9]")); } private Kernel buildTextKernel() { diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/ToolCallBehaviourTest.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/ToolCallBehaviourTest.java similarity index 97% rename from java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/ToolCallBehaviourTest.java rename to api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/ToolCallBehaviourTest.java index 78328308..852f5578 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/ToolCallBehaviourTest.java +++ b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/ToolCallBehaviourTest.java @@ -13,6 +13,7 @@ import com.github.tomakehurst.wiremock.junit5.WireMockTest; import com.github.tomakehurst.wiremock.matching.ContainsPattern; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatMessageContent; import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIFunctionToolCall; import com.microsoft.semantickernel.implementation.CollectionUtil; @@ -26,6 +27,7 @@ import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageTextContent; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -73,9 +75,7 @@ public void nonAutoInvokedIsNotCalled() throws NoSuchMethodException { ChatHistory messages = new ChatHistory(); messages.addMessage( - new ChatMessageContent<>( - AuthorRole.USER, - "Call A function")); + ChatMessageTextContent.userMessage("Call A function")); List> result = chatCompletionService .getChatMessageContentsAsync( @@ -124,9 +124,7 @@ public void toolIsInvoked() throws NoSuchMethodException { ChatHistory messages = new ChatHistory(); messages.addMessage( - new ChatMessageContent<>( - AuthorRole.USER, - "Call A function")); + ChatMessageTextContent.userMessage("Call A function")); List> result = chatCompletionService .getChatMessageContentsAsync( @@ -275,7 +273,7 @@ private ChatCompletionService getChatCompletionService() { .endpoint("http://localhost:" + wm.port() + "/") .buildAsyncClient(); - return ChatCompletionService.builder() + return OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId("gpt-35-turbo-2") .build(); diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WireMockUtil.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WireMockUtil.java similarity index 97% rename from java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WireMockUtil.java rename to api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WireMockUtil.java index 5bf39de8..93adcd81 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WireMockUtil.java +++ b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WireMockUtil.java @@ -116,7 +116,7 @@ public static void mockChatCompletionResponse( WireMock.reset(); WireMock.stubFor(WireMock .post(new UrlPathPattern( - new RegexPattern("/openai/deployments/text-davinci-003/chat/completions"), true)) + new RegexPattern("/openai/deployments/gpt-35-turbo/chat/completions"), true)) .withRequestBody(WireMock.matching(".*" + regexMatcher + ".*")) .willReturn(WireMock.ok() .withBody(body))); diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WiremockExamplesIT.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WiremockExamplesIT.java similarity index 100% rename from java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WiremockExamplesIT.java rename to api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WiremockExamplesIT.java diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WiremockRecord.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WiremockRecord.java similarity index 100% rename from java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WiremockRecord.java rename to api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/WiremockRecord.java diff --git a/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/connectors/memory/Hotel.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/connectors/memory/Hotel.java new file mode 100644 index 00000000..e43842fc --- /dev/null +++ b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/connectors/memory/Hotel.java @@ -0,0 +1,59 @@ +package com.microsoft.semantickernel.tests.connectors.memory; + +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordDataAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordKeyAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordVectorAttribute; + +import java.util.List; + +public class Hotel { + @VectorStoreRecordKeyAttribute + private final String id; + @VectorStoreRecordDataAttribute + private final String name; + @VectorStoreRecordDataAttribute + private final int code; + @VectorStoreRecordDataAttribute(hasEmbedding = true, embeddingFieldName = "descriptionEmbedding") + private final String description; + @VectorStoreRecordVectorAttribute(dimensions = 3) + private final List descriptionEmbedding; + @VectorStoreRecordDataAttribute + private final double rating; + + public Hotel() { + this(null, null, 0, null, null, 0.0); + } + + public Hotel(String id, String name, int code, String description, List descriptionEmbedding, double rating) { + this.id = id; + this.name = name; + this.code = code; + this.description = description; + this.descriptionEmbedding = descriptionEmbedding; + this.rating = rating; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public List getDescriptionEmbedding() { + return descriptionEmbedding; + } + + public double getRating() { + return rating; + } +} diff --git a/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/connectors/memory/redis/RedisVectorStoreRecordCollectionTest.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/connectors/memory/redis/RedisVectorStoreRecordCollectionTest.java new file mode 100644 index 00000000..db3b6d6c --- /dev/null +++ b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/connectors/memory/redis/RedisVectorStoreRecordCollectionTest.java @@ -0,0 +1,305 @@ +package com.microsoft.semantickernel.tests.connectors.memory.redis; + +import com.microsoft.semantickernel.connectors.data.redis.RedisVectorStoreRecordCollection; +import com.microsoft.semantickernel.connectors.data.redis.RedisVectorStoreRecordCollectionOptions; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDataField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordKeyField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordVectorField; +import com.microsoft.semantickernel.data.recordoptions.GetRecordOptions; +import com.microsoft.semantickernel.tests.connectors.memory.Hotel; +import com.redis.testcontainers.RedisContainer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import redis.clients.jedis.JedisPooled; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +@Testcontainers +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class RedisVectorStoreRecordCollectionTest { + + @Container private static final RedisContainer redisContainer = new RedisContainer("redis/redis-stack:latest"); + + private static final Map> optionsMap = new HashMap<>(); + + public enum RecordCollectionOptions { + DEFAULT, WITH_CUSTOM_DEFINITION + } + + @BeforeAll + static void setup() { + optionsMap.put(RecordCollectionOptions.DEFAULT, RedisVectorStoreRecordCollectionOptions.builder() + .withRecordClass(Hotel.class) + .build()); + + List fields = new ArrayList<>(); + fields.add(VectorStoreRecordKeyField.builder() + .withName("id") + .build()); + fields.add(VectorStoreRecordDataField.builder() + .withName("name") + .withFieldType(String.class) + .build()); + fields.add(VectorStoreRecordDataField.builder() + .withName("code") + .withFieldType(Integer.class) + .build()); + fields.add(VectorStoreRecordDataField.builder() + .withName("description") + .withFieldType(String.class) + .withHasEmbedding(true) + .withEmbeddingFieldName("descriptionEmbedding") + .build()); + fields.add(VectorStoreRecordVectorField.builder() + .withName("descriptionEmbedding") + .withDimensions(768) + .build()); + fields.add(VectorStoreRecordDataField.builder() + .withName("rating") + .withFieldType(Double.class) + .build()); + VectorStoreRecordDefinition recordDefinition = VectorStoreRecordDefinition.fromFields(fields); + + optionsMap.put(RecordCollectionOptions.WITH_CUSTOM_DEFINITION, RedisVectorStoreRecordCollectionOptions.builder() + .withRecordClass(Hotel.class) + .withRecordDefinition(recordDefinition) + .build()); + } + + private RedisVectorStoreRecordCollection buildrecordCollection(@Nonnull RedisVectorStoreRecordCollectionOptions options, @Nonnull String collectionName) { + return new RedisVectorStoreRecordCollection<>(new JedisPooled(redisContainer.getRedisURI()), collectionName, RedisVectorStoreRecordCollectionOptions.builder() + .withRecordClass(options.getRecordClass()) + .withVectorStoreRecordMapper(options.getVectorStoreRecordMapper()) + .withRecordDefinition(options.getRecordDefinition()) + .withPrefixCollectionName(options.isPrefixCollectionName()) + .build()); + } + + private List getHotels() { + return List.of( + new Hotel("id_1", "Hotel 1", 1, "Hotel 1 description", Arrays.asList(1.0f, 2.0f, 3.0f), 4.0), + new Hotel("id_2", "Hotel 2", 2, "Hotel 2 description", Arrays.asList(1.0f, 2.0f, 3.0f), 3.0), + new Hotel("id_3", "Hotel 3", 3, "Hotel 3 description", Arrays.asList(1.0f, 2.0f, 3.0f), 5.0), + new Hotel("id_4", "Hotel 4", 4, "Hotel 4 description", Arrays.asList(1.0f, 2.0f, 3.0f), 4.0), + new Hotel("id_5", "Hotel 5", 5, "Hotel 5 description", Arrays.asList(1.0f, 2.0f, 3.0f), 5.0) + ); + } + + @Order(1) + @ParameterizedTest + @EnumSource(RecordCollectionOptions.class) + public void buildrecordCollection(RecordCollectionOptions options) { + assertNotNull(buildrecordCollection(optionsMap.get(options), options.name())); + } + + @Order(2) + @ParameterizedTest + @EnumSource(RecordCollectionOptions.class) + public void createCollectionAsync(RecordCollectionOptions options) { + RedisVectorStoreRecordCollection recordCollection = buildrecordCollection(optionsMap.get(options), options.name()); + + assertEquals(false, recordCollection.collectionExistsAsync().block()); + recordCollection.createCollectionAsync().block(); + assertEquals(true, recordCollection.collectionExistsAsync().block()); + } + + @Test + public void deleteCollectionAsync() { + RedisVectorStoreRecordCollection recordCollection = buildrecordCollection(optionsMap.get(RecordCollectionOptions.DEFAULT), "deleteCollectionAsync"); + + assertEquals(false, recordCollection.collectionExistsAsync().block()); + recordCollection.createCollectionAsync().block(); + recordCollection.deleteCollectionAsync().block(); + assertEquals(false, recordCollection.collectionExistsAsync().block()); + } + + @ParameterizedTest + @EnumSource(RecordCollectionOptions.class) + public void upsertAndGetRecordAsync(RecordCollectionOptions options) { + RedisVectorStoreRecordCollection recordCollection = buildrecordCollection(optionsMap.get(options), options.name()); + + List hotels = getHotels(); + for (Hotel hotel : hotels) { + recordCollection.upsertAsync(hotel, null).block(); + } + + for (Hotel hotel : hotels) { + Hotel retrievedHotel = recordCollection.getAsync(hotel.getId(), null).block(); + assertNotNull(retrievedHotel); + assertEquals(hotel.getId(), retrievedHotel.getId()); + } + } + + @ParameterizedTest + @EnumSource(RecordCollectionOptions.class) + public void getBatchAsync(RecordCollectionOptions options) { + RedisVectorStoreRecordCollection recordCollection = buildrecordCollection(optionsMap.get(options), options.name()); + + List hotels = getHotels(); + for (Hotel hotel : hotels) { + recordCollection.upsertAsync(hotel, null).block(); + } + + List ids = new ArrayList<>(); + hotels.forEach(hotel -> ids.add(hotel.getId())); + + List retrievedHotels = recordCollection.getBatchAsync(ids, null).block(); + + assertNotNull(retrievedHotels); + assertEquals(hotels.size(), retrievedHotels.size()); + for (int i = 0; i < hotels.size(); i++) { + assertEquals(hotels.get(i).getId(), retrievedHotels.get(i).getId()); + } + } + + @ParameterizedTest + @EnumSource(RecordCollectionOptions.class) + public void upsertBatchAsync(RecordCollectionOptions options) { + RedisVectorStoreRecordCollection recordCollection = buildrecordCollection(optionsMap.get(options), options.name()); + + List hotels = getHotels(); + List keys = recordCollection.upsertBatchAsync(hotels, null).block(); + assertNotNull(keys); + + List retrievedHotels = (List) recordCollection.getBatchAsync(keys, null).block(); + + assertNotNull(retrievedHotels); + assertEquals(hotels.size(), retrievedHotels.size()); + for (int i = 0; i < hotels.size(); i++) { + assertEquals(hotels.get(i).getId(), retrievedHotels.get(i).getId()); + } + } + + @ParameterizedTest + @EnumSource(RecordCollectionOptions.class) + public void deleteAsync(RecordCollectionOptions options) { + RedisVectorStoreRecordCollection recordCollection = buildrecordCollection(optionsMap.get(options), options.name()); + + List hotels = getHotels(); + recordCollection.upsertBatchAsync(hotels, null).block(); + + for (Hotel hotel : hotels) { + recordCollection.deleteAsync(hotel.getId(), null).block(); + Hotel retrievedHotel = recordCollection.getAsync(hotel.getId(), null).block(); + assertNull(retrievedHotel); + } + } + + @ParameterizedTest + @EnumSource(RecordCollectionOptions.class) + public void deleteBatchAsync(RecordCollectionOptions options) { + RedisVectorStoreRecordCollection recordCollection = buildrecordCollection(optionsMap.get(options), options.name()); + + List hotels = getHotels(); + recordCollection.upsertBatchAsync(hotels, null).block(); + + List ids = new ArrayList<>(); + hotels.forEach(hotel -> ids.add(hotel.getId())); + + recordCollection.deleteBatchAsync(ids, null).block(); + + for (String id : ids) { + Hotel retrievedHotel = recordCollection.getAsync(id, null).block(); + assertNull(retrievedHotel); + } + } + + @ParameterizedTest + @EnumSource(RecordCollectionOptions.class) + public void getAsyncWithVectors(RecordCollectionOptions options) { + RedisVectorStoreRecordCollection recordCollection = buildrecordCollection(optionsMap.get(options), options.name()); + + List hotels = getHotels(); + recordCollection.upsertBatchAsync(hotels, null).block(); + + for (Hotel hotel : hotels) { + Hotel retrievedHotel = recordCollection.getAsync(hotel.getId(), null).block(); + assertNotNull(retrievedHotel); + assertNotNull(retrievedHotel.getDescriptionEmbedding()); + assertEquals(hotel.getId(), retrievedHotel.getId()); + assertEquals(hotel.getDescription(), retrievedHotel.getDescription()); + } + } + + @ParameterizedTest + @EnumSource(RecordCollectionOptions.class) + public void getBatchAsyncWithVectors(RecordCollectionOptions options) { + RedisVectorStoreRecordCollection recordCollection = buildrecordCollection(optionsMap.get(options), options.name()); + + List hotels = getHotels(); + recordCollection.upsertBatchAsync(hotels, null).block(); + + List ids = new ArrayList<>(); + hotels.forEach(hotel -> ids.add(hotel.getId())); + + List retrievedHotels = recordCollection.getBatchAsync(ids, null).block(); + + assertNotNull(retrievedHotels); + assertEquals(hotels.size(), retrievedHotels.size()); + for (int i = 0; i < hotels.size(); i++) { + assertEquals(hotels.get(i).getId(), retrievedHotels.get(i).getId()); + assertEquals(hotels.get(i).getDescription(), retrievedHotels.get(i).getDescription()); + assertNotNull(retrievedHotels.get(i).getDescriptionEmbedding()); + } + } + + @ParameterizedTest + @EnumSource(RecordCollectionOptions.class) + public void getAsyncWithNoVectors(RecordCollectionOptions options) { + RedisVectorStoreRecordCollection recordCollection = buildrecordCollection(optionsMap.get(options), options.name()); + + List hotels = getHotels(); + recordCollection.upsertBatchAsync(hotels, null).block(); + + GetRecordOptions getRecordOptions = GetRecordOptions.builder().includeVectors(false).build(); + for (Hotel hotel : hotels) { + Hotel retrievedHotel = recordCollection.getAsync(hotel.getId(), getRecordOptions).block(); + assertNotNull(retrievedHotel); + assertNull(retrievedHotel.getDescriptionEmbedding()); + assertEquals(hotel.getId(), retrievedHotel.getId()); + assertEquals(hotel.getDescription(), retrievedHotel.getDescription()); + } + } + + @ParameterizedTest + @EnumSource(RecordCollectionOptions.class) + public void getBatchAsyncWithNoVectors(RecordCollectionOptions options) { + RedisVectorStoreRecordCollection recordCollection = buildrecordCollection(optionsMap.get(options), options.name()); + + List hotels = getHotels(); + recordCollection.upsertBatchAsync(hotels, null).block(); + + GetRecordOptions getRecordOptions = GetRecordOptions.builder().includeVectors(false).build(); + List ids = new ArrayList<>(); + hotels.forEach(hotel -> ids.add(hotel.getId())); + + List retrievedHotels = recordCollection.getBatchAsync(ids, getRecordOptions).block(); + + assertNotNull(retrievedHotels); + assertEquals(hotels.size(), retrievedHotels.size()); + for (int i = 0; i < hotels.size(); i++) { + assertEquals(hotels.get(i).getId(), retrievedHotels.get(i).getId()); + assertEquals(hotels.get(i).getDescription(), retrievedHotels.get(i).getDescription()); + assertNull(retrievedHotels.get(i).getDescriptionEmbedding()); + } + } +} diff --git a/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/connectors/memory/redis/RedisVectorStoreTest.java b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/connectors/memory/redis/RedisVectorStoreTest.java new file mode 100644 index 00000000..96341053 --- /dev/null +++ b/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/tests/connectors/memory/redis/RedisVectorStoreTest.java @@ -0,0 +1,48 @@ +package com.microsoft.semantickernel.tests.connectors.memory.redis; + +import com.microsoft.semantickernel.connectors.data.redis.RedisVectorStore; +import com.microsoft.semantickernel.connectors.data.redis.RedisVectorStoreOptions; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; +import com.microsoft.semantickernel.tests.connectors.memory.Hotel; +import com.redis.testcontainers.RedisContainer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import redis.clients.jedis.JedisPooled; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +public class RedisVectorStoreTest { + @Container + private static final RedisContainer redisContainer = new RedisContainer("redis/redis-stack:latest"); + private static JedisPooled jedis; + + @BeforeAll + public static void setUp() { + jedis = new JedisPooled(redisContainer.getRedisURI()); + } + + @Test + public void getCollectionNamesAsync() { + RedisVectorStore vectorStore = new RedisVectorStore(jedis, new RedisVectorStoreOptions()); + List collectionNames = Arrays.asList("collection1", "collection2", "collection3"); + + for (String collectionName : collectionNames) { + vectorStore.getCollection(collectionName, Hotel.class, null).createCollectionAsync().block(); + } + + List retrievedCollectionNames = vectorStore.getCollectionNamesAsync().block(); + assertNotNull(retrievedCollectionNames); + assertEquals(collectionNames.size(), retrievedCollectionNames.size()); + for (String collectionName : collectionNames) { + assertTrue(retrievedCollectionNames.contains(collectionName)); + } + } +} diff --git a/java/api-test/integration-tests/src/test/resources/log4j2.xml b/api-test/integration-tests/src/test/resources/log4j2.xml similarity index 100% rename from java/api-test/integration-tests/src/test/resources/log4j2.xml rename to api-test/integration-tests/src/test/resources/log4j2.xml diff --git a/java/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo-2_chat_completions-12b6744e-443f-4fe7-82e2-55cc41195ff1.json b/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo-2_chat_completions-12b6744e-443f-4fe7-82e2-55cc41195ff1.json similarity index 100% rename from java/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo-2_chat_completions-12b6744e-443f-4fe7-82e2-55cc41195ff1.json rename to api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo-2_chat_completions-12b6744e-443f-4fe7-82e2-55cc41195ff1.json diff --git a/java/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo-2_chat_completions-71b07779-49a8-44e5-a60b-ee5b0a3ad697.json b/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo-2_chat_completions-71b07779-49a8-44e5-a60b-ee5b0a3ad697.json similarity index 100% rename from java/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo-2_chat_completions-71b07779-49a8-44e5-a60b-ee5b0a3ad697.json rename to api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo-2_chat_completions-71b07779-49a8-44e5-a60b-ee5b0a3ad697.json diff --git a/java/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo_chat_completions-0c8046c5-74ad-4836-8aa9-09da60f367a2.json b/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo_chat_completions-0c8046c5-74ad-4836-8aa9-09da60f367a2.json similarity index 100% rename from java/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo_chat_completions-0c8046c5-74ad-4836-8aa9-09da60f367a2.json rename to api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_gpt-35-turbo_chat_completions-0c8046c5-74ad-4836-8aa9-09da60f367a2.json diff --git a/java/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_text-davinci-003_completions-0215b128-4822-4368-ac3d-2f580a221f00.json b/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_text-davinci-003_completions-0215b128-4822-4368-ac3d-2f580a221f00.json similarity index 100% rename from java/api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_text-davinci-003_completions-0215b128-4822-4368-ac3d-2f580a221f00.json rename to api-test/integration-tests/src/test/resources/wiremock/mappings/deployments_text-davinci-003_completions-0215b128-4822-4368-ac3d-2f580a221f00.json diff --git a/java/api-test/pom.xml b/api-test/pom.xml similarity index 98% rename from java/api-test/pom.xml rename to api-test/pom.xml index 83f5f2f4..7b49dd77 100644 --- a/java/api-test/pom.xml +++ b/api-test/pom.xml @@ -6,7 +6,7 @@ com.microsoft.semantic-kernel semantickernel-parent - 1.1.5 + 1.2.0 ../pom.xml diff --git a/java/api-test/spotbugs-exclude.xml b/api-test/spotbugs-exclude.xml similarity index 100% rename from java/api-test/spotbugs-exclude.xml rename to api-test/spotbugs-exclude.xml diff --git a/java/connectors/semantickernel-connectors-memory-redis/pom.xml b/connectors/semantickernel-connectors-memory-redis/pom.xml similarity index 100% rename from java/connectors/semantickernel-connectors-memory-redis/pom.xml rename to connectors/semantickernel-connectors-memory-redis/pom.xml diff --git a/java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/JsonMemoryRecord.java b/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/JsonMemoryRecord.java similarity index 100% rename from java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/JsonMemoryRecord.java rename to connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/JsonMemoryRecord.java diff --git a/java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisException.java b/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisException.java similarity index 100% rename from java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisException.java rename to connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisException.java diff --git a/java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisIndexSchemaParams.java b/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisIndexSchemaParams.java similarity index 100% rename from java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisIndexSchemaParams.java rename to connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisIndexSchemaParams.java diff --git a/java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisMemoryRecord.java b/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisMemoryRecord.java similarity index 100% rename from java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisMemoryRecord.java rename to connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisMemoryRecord.java diff --git a/java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisMemoryStore.java b/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisMemoryStore.java similarity index 100% rename from java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisMemoryStore.java rename to connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisMemoryStore.java diff --git a/java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisVectorDistanceMetric.java b/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisVectorDistanceMetric.java similarity index 100% rename from java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisVectorDistanceMetric.java rename to connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisVectorDistanceMetric.java diff --git a/java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisVectorType.java b/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisVectorType.java similarity index 100% rename from java/connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisVectorType.java rename to connectors/semantickernel-connectors-memory-redis/src/main/java/com/microsoft/semantickernel/connectors/memory/redis/RedisVectorType.java diff --git a/java/connectors/semantickernel-connectors-web-bing/pom.xml b/connectors/semantickernel-connectors-web-bing/pom.xml similarity index 100% rename from java/connectors/semantickernel-connectors-web-bing/pom.xml rename to connectors/semantickernel-connectors-web-bing/pom.xml diff --git a/java/eclipse-formatter.xml b/eclipse-formatter.xml similarity index 100% rename from java/eclipse-formatter.xml rename to eclipse-formatter.xml diff --git a/java/CHANGELOG.md b/java/CHANGELOG.md deleted file mode 100644 index d0c864ad..00000000 --- a/java/CHANGELOG.md +++ /dev/null @@ -1,34 +0,0 @@ -# 1.1.5 - -- Fix bug with removing new lines on function parameters on Windows -- Fix bug forming serializing arguments to tool calls - -# 1.1.3 - -- Fix bug appending plugin name to tool calls -- Improve exception handling in OpenAIChatCompletion - -# 1.1.2 - -- Upgrade azure-identity to 1.12.1 -- Remove fixed netty version in bom - -# 1.1.1 - -- Upgrade azure-ai-openai to 1.0.0-beta.8 - -# 1.1.0 - -### Breaking Changes - -- `ChatHistory` no longer has a default message, see below for more details. - -### Api Changes - - Allow setting deployment name in addition to modelId on AI services. - - Remove default message of "Assistant is a large language model" from ChatHistory - - **This is a breaking change if you were relying on the default message in your code** - - Add InvocationReturnMode and rework OpenAi chat completion to allow configuring what data is returned from Chat requests - -### Other -- Reorganize example projects and documentation structure. -- Number of sample updates and bug fixes. \ No newline at end of file diff --git a/java/samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/AzureOpenAIConnectionPropertiesTest.java b/java/samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/AzureOpenAIConnectionPropertiesTest.java deleted file mode 100644 index 76870b8f..00000000 --- a/java/samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/AzureOpenAIConnectionPropertiesTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.microsoft.semantickernel.azureopenai; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import com.microsoft.semantickernel.starter.AzureOpenAIConnectionProperties; -import com.microsoft.semantickernel.starter.SemanticKernelAutoConfiguration; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.test.context.ActiveProfiles; - -@ActiveProfiles("test") -public class AzureOpenAIConnectionPropertiesTest { - - ApplicationContextRunner contextRunner = - new ApplicationContextRunner() - .withPropertyValues( - // @formatter:off - "client.azureopenai.key=TEST_KEY", - "client.azureopenai.endpoint=TEST_ENDPOINT", - "client.azureopenai.deploymentname=TEST_DEPLOYMENT_NAME" - - // @formatter:on - ) - .withConfiguration( - AutoConfigurations.of(SemanticKernelAutoConfiguration.class)); - - @Test - public void ConnectionPropertiesTest() { - contextRunner.run( - context -> { - AzureOpenAIConnectionProperties props = - context.getBean(AzureOpenAIConnectionProperties.class); - assertNotNull(props.getEndpoint()); - assertNotNull(props.getKey()); - assertNotNull(props.getDeploymentName()); - }); - } -} diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethodTest.java b/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethodTest.java deleted file mode 100644 index a1634078..00000000 --- a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethodTest.java +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -package com.microsoft.semantickernel.semanticfunctions; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.microsoft.semantickernel.Kernel; -import com.microsoft.semantickernel.contextvariables.ContextVariableTypes; -import com.microsoft.semantickernel.orchestration.FunctionResult; -import com.microsoft.semantickernel.plugin.KernelPlugin; -import com.microsoft.semantickernel.plugin.KernelPluginFactory; -import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; -import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.List; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -public class KernelFunctionFromMethodTest { - - public KernelFunctionFromMethodTest() { - } - - public static class ExamplePlugin { - - @DefineKernelFunction(name = "sqrt", description = "Take the square root of a number") - public static double sqrt( - @KernelFunctionParameter(name = "number1", description = "The number to take a square root of", type = double.class) double number1) { - return Math.sqrt(number1); - } - } - - @Test - void typeConversionOnMethodCall() { - KernelPlugin plugin = KernelPluginFactory.createFromObject( - new ExamplePlugin(), "ExamplePlugin"); - - Kernel kernel = Kernel.builder().build(); - - FunctionResult result = plugin - .get("sqrt") - .invokeAsync(kernel) - .withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(String.class)) - .withArguments( - KernelFunctionArguments.builder() - .withVariable("number1", "12.0") - .build()) - .block(); - - Assertions.assertEquals("3.4641016151377544", result.getResult()); - } - - @Test - void testCreate() throws Exception { - Method method = String.class.getMethod("concat", String.class); - String pluginName = "test-plugin"; - String functionName = "concat"; - String description = "concatenate two strings"; - List parameters = Collections.singletonList(InputVariable.build( - "string1", String.class, "first string to concatenate", null, null, true)); - OutputVariable returnParameter = new OutputVariable<>( - "concatenated strings", String.class); - KernelFunction result = KernelFunctionFromMethod.create(method, "test", pluginName, - functionName, description, parameters, returnParameter); - assertEquals(functionName, result.getName()); - assertEquals(pluginName, result.getPluginName()); - assertEquals(description, result.getDescription()); - assertEquals(parameters, result.getMetadata().getParameters()); - assertEquals(returnParameter, result.getMetadata().getOutputVariableType()); - } - - /** - * Test of builder method, of class KernelFunctionFromMethod. - */ - @Test - void testBuilder() { - String pluginName = null; - String functionName = "concat"; - String description = "concatenate two strings"; - List parameters = Collections - .singletonList(new InputVariable("string1", - "java.lang.String", "first string to concatenate", null, true, null)); - OutputVariable returnParameter = new OutputVariable<>("java.lang.String", - "concatenated strings"); - KernelFunction result = new KernelFunctionFromPrompt.Builder() - .withName(functionName) - .withDescription(description) - .withInputParameters(parameters) - .withOutputVariable(returnParameter) - .withPromptTemplate((kernel, args, context) -> Mono.empty()) - .build(); - assertEquals(functionName, result.getName()); - assertEquals(pluginName, result.getPluginName()); - assertEquals(description, result.getDescription()); - // TODO: This assert fails because getParameters is a List>, not an List - // This feels like it's broken. Until this is fixed, we can compare the types - // assertEquals(parameters, result.getMetadata().getParameters()); - assertEquals(parameters.size(), result.getMetadata().getParameters().size()); - for (int i = 0; i < parameters.size(); i++) { - assertEquals(parameters.get(i).getDescription(), - result.getMetadata().getParameters().get(i).getDescription()); - assertEquals(parameters.get(i).getName(), - result.getMetadata().getParameters().get(i).getName()); - assertEquals(parameters.get(i).getType(), - result.getMetadata().getParameters().get(i).getType()); - assertEquals(parameters.get(i).isRequired(), - result.getMetadata().getParameters().get(i).isRequired()); - } - assertEquals(returnParameter, result.getMetadata().getOutputVariableType()); - assertEquals(returnParameter.getType(), - result.getMetadata().getOutputVariableType().getType()); - assertEquals(returnParameter.getDescription(), - result.getMetadata().getOutputVariableType().getDescription()); - } - - /** - * Test of invokeAsync method, of class KernelFunctionFromMethod. - */ - @Test - @Disabled("TODO: needs mocked http server") - void testInvokeAsync() { - } - -} \ No newline at end of file diff --git a/java/mvnw b/mvnw similarity index 100% rename from java/mvnw rename to mvnw diff --git a/java/mvnw.cmd b/mvnw.cmd similarity index 100% rename from java/mvnw.cmd rename to mvnw.cmd diff --git a/java/pom.xml b/pom.xml similarity index 96% rename from java/pom.xml rename to pom.xml index 267fedbb..313a7869 100644 --- a/java/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.microsoft.semantic-kernel semantickernel-parent - 1.1.5 + 1.2.0 pom https://www.github.com/microsoft/semantic-kernel @@ -68,9 +68,12 @@ semantickernel-bom - aiservices/openai - semantickernel-api + semantickernel-experimental + + aiservices/openai + aiservices/google + aiservices/huggingface @@ -649,7 +652,7 @@ 4.30 - eclipse-formatter.xml + ./eclipse-formatter.xml + + com.github.spotbugs + spotbugs-maven-plugin + + true + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + + org.codehaus.mojo + exec-maven-plugin + + + run-sample + + java + + + + + com.microsoft.semantickernel.samples.syntaxexamples.${sample} + + + + + diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/connectors/web/bing/BingConnector.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/connectors/web/bing/BingConnector.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/connectors/web/bing/BingConnector.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/connectors/web/bing/BingConnector.java diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/demos/lights/App.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/demos/lights/App.java new file mode 100644 index 00000000..08fb2697 --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/demos/lights/App.java @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.demos.lights; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.credential.KeyCredential; +import com.google.gson.Gson; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; +import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter; +import com.microsoft.semantickernel.contextvariables.ContextVariableTypes; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.orchestration.InvocationContext.Builder; +import com.microsoft.semantickernel.orchestration.InvocationReturnMode; +import com.microsoft.semantickernel.orchestration.ToolCallBehavior; +import com.microsoft.semantickernel.plugin.KernelPlugin; +import com.microsoft.semantickernel.plugin.KernelPluginFactory; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import java.util.List; +import java.util.Scanner; + +public class App { + + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); + + // Only required if AZURE_CLIENT_KEY is set + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + private static final String MODEL_ID = System.getenv() + .getOrDefault("MODEL_ID", "gpt-4o"); + + public static void main(String[] args) throws Exception { + + OpenAIAsyncClient client; + + if (AZURE_CLIENT_KEY != null) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(AZURE_CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + // Create your AI service client + ChatCompletionService chatService = OpenAIChatCompletion.builder() + .withModelId(MODEL_ID) + .withOpenAIAsyncClient(client) + .build(); + // Create a plugin (the LightsPlugin class is defined separately) + KernelPlugin lightPlugin = KernelPluginFactory.createFromObject(new LightsPlugin(), + "LightsPlugin"); + + // Create a kernel with Azure OpenAI chat completion and plugin + Kernel.Builder builder = Kernel.builder(); + builder.withAIService(ChatCompletionService.class, chatService); + builder.withPlugin(lightPlugin); + // Build the kernel + Kernel kernel = builder.build(); + + ChatCompletionService chatCompletionService = kernel.getService( + ChatCompletionService.class); + + ContextVariableTypes + .addGlobalConverter(ContextVariableTypeConverter.builder(LightModel.class) + .toPromptString(new Gson()::toJson) + .build()); + + // Enable planning + InvocationContext invocationContext = new Builder() + .withReturnMode(InvocationReturnMode.LAST_MESSAGE_ONLY) + .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true)) + .withContextVariableConverter(ContextVariableTypeConverter.builder(LightModel.class) + .toPromptString(new Gson()::toJson) + .build()) + .build(); + + // Create a history to store the conversation + ChatHistory history = new ChatHistory(); + // Initiate a back-and-forth chat + Scanner scanner = new Scanner(System.in); + String userInput; + do { + // Collect user input + System.out.print("User > "); + userInput = scanner.nextLine(); + // Add user input + history.addUserMessage(userInput); + List> results = chatCompletionService.getChatMessageContentsAsync( + history, kernel, invocationContext).block(); + for (ChatMessageContent result : results) { + // Print the results + if (result.getAuthorRole() == AuthorRole.ASSISTANT && result.getContent() != null) { + System.out.println("Assistant > " + result); + } + // Add the message from the agent to the chat history + history.addMessage(result); + } + } while (userInput != null && !userInput.isEmpty()); + } +} diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/demos/lights/LightModel.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/demos/lights/LightModel.java new file mode 100644 index 00000000..072763a9 --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/demos/lights/LightModel.java @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.demos.lights; + +public class LightModel { + + private int id; + private String name; + private Boolean isOn; + + public LightModel(int id, String name, Boolean isOn) { + this.id = id; + this.name = name; + this.isOn = isOn; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Boolean getIsOn() { + return isOn; + } + + public void setIsOn(Boolean isOn) { + this.isOn = isOn; + } +} diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/demos/lights/LightsPlugin.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/demos/lights/LightsPlugin.java new file mode 100644 index 00000000..55d70b02 --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/demos/lights/LightsPlugin.java @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.demos.lights; + +import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; +import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class LightsPlugin { + + // Mock data for the lights + private final List lights = new ArrayList<>(); + + public LightsPlugin() { + lights.add(new LightModel(1, "Table Lamp", false)); + lights.add(new LightModel(2, "Porch light", false)); + lights.add(new LightModel(3, "Chandelier", true)); + } + + @DefineKernelFunction(name = "get_lights", description = "Gets a list of lights and their current state") + public List getLights() { + System.out.println("Getting lights"); + return lights; + } + + @DefineKernelFunction(name = "change_state", description = "Changes the state of the light") + public LightModel changeState( + @KernelFunctionParameter(name = "id", description = "The ID of the light to change") int id, + @KernelFunctionParameter(name = "isOn", description = "The new state of the light") boolean isOn) { + System.out.println("Changing light " + id + " " + isOn); + Optional light = lights.stream() + .filter(l -> l.getId() == id) + .findFirst(); + + if (light.isEmpty()) { + throw new IllegalArgumentException("Light not found"); + } + light.get().setIsOn(isOn); + + return light.get(); + } +} \ No newline at end of file diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/ConversationSummaryPlugin.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/ConversationSummaryPlugin.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/ConversationSummaryPlugin.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/ConversationSummaryPlugin.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/MathPlugin.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/MathPlugin.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/MathPlugin.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/MathPlugin.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/PromptFunctionConstants.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/PromptFunctionConstants.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/PromptFunctionConstants.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/PromptFunctionConstants.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/TimePlugin.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/TimePlugin.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/TimePlugin.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/TimePlugin.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/text/TextPlugin.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/text/TextPlugin.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/text/TextPlugin.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/text/TextPlugin.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/web/SearchUrlPlugin.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/web/SearchUrlPlugin.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/web/SearchUrlPlugin.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/web/SearchUrlPlugin.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/web/WebSearchEnginePlugin.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/web/WebSearchEnginePlugin.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/web/WebSearchEnginePlugin.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/web/WebSearchEnginePlugin.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example11_WebSearchQueries.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example11_WebSearchQueries.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example11_WebSearchQueries.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example11_WebSearchQueries.java diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example20_HuggingFace.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example20_HuggingFace.java new file mode 100644 index 00000000..a388c130 --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example20_HuggingFace.java @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.syntaxexamples; + +import com.azure.core.credential.AzureKeyCredential; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.huggingface.HuggingFaceClient; +import com.microsoft.semantickernel.aiservices.huggingface.services.HuggingFaceTextGenerationService; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt; +import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; + +public class Example20_HuggingFace { + + private static final String HUGGINGFACE_CLIENT_KEY = System.getenv("HUGGINGFACE_CLIENT_KEY"); + private static final String HUGGINGFACE_CLIENT_ENDPOINT = System.getenv( + "HUGGINGFACE_CLIENT_ENDPOINT"); + + public static void main(String[] args) { + //runConversationApiExampleAsync(); + runInferenceApiExampleAsync(); + } + + public static void runInferenceApiExampleAsync() { + System.out.println("\n======== HuggingFace Inference API example ========\n"); + + HuggingFaceClient client = HuggingFaceClient.builder() + .credential(new AzureKeyCredential(HUGGINGFACE_CLIENT_KEY)) + .endpoint(HUGGINGFACE_CLIENT_ENDPOINT) + .build(); + + var chatCompletion = HuggingFaceTextGenerationService.builder() + .withModelId("gpt2-24") + .withHuggingFaceClient(client) + .build(); + + Kernel kernel = Kernel.builder() + .withAIService(TextGenerationService.class, chatCompletion) + .build(); + + var questionAnswerFunction = KernelFunctionFromPrompt.builder() + .withTemplate("Question: {{$input}}; Answer:") + .build(); + + var result = kernel.invokeAsync(questionAnswerFunction) + .withArguments( + KernelFunctionArguments.builder() + .withVariable("input", "What is New York?") + .build()) + .withResultType(String.class) + .block(); + + System.out.println(result.getResult()); + } + + /* + * + * public static void runConversationApiExampleAsync() { + * System.out.println("\n======== HuggingFace Inference API example ========\n"); + * + * HuggingFaceClient client = HuggingFaceClient.builder() + * .credential(new AzureKeyCredential(HUGGINGFACE_CLIENT_KEY)) + * .endpoint(HUGGINGFACE_CLIENT_ENDPOINT) + * .build(); + * + * var chatCompletion = HuggingFaceChatCompletionService.builder() + * .withModelId("msft-dialogpt-medium-13") + * .withHuggingFaceClient(client) + * .build(); + * + * Kernel kernel = Kernel.builder() + * .withAIService(ChatCompletionService.class, chatCompletion) + * .build(); + * + * var questionAnswerFunction = KernelFunctionFromPrompt.builder() + * .withTemplate(""" + * Assistant is a large language model that answers questions. + * What is your question? + * {{$input}} + * """) + * .build(); + * + * var result = kernel.invokeAsync(questionAnswerFunction) + * .withArguments( + * KernelFunctionArguments.builder() + * .withVariable("input", "What is New York?") + * .build() + * ) + * .withResultType(String.class) + * .block(); + * + * System.out.println(result.getResult()); + * } + * + */ +} diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example42_KernelBuilder.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example42_KernelBuilder.java similarity index 87% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example42_KernelBuilder.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example42_KernelBuilder.java index cc35a564..b95da431 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example42_KernelBuilder.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example42_KernelBuilder.java @@ -7,6 +7,7 @@ import com.azure.core.credential.KeyCredential; import com.azure.core.http.HttpClient; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.plugin.KernelPluginFactory; import com.microsoft.semantickernel.samples.plugins.ConversationSummaryPlugin; import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; @@ -26,7 +27,7 @@ public static void main(String[] args) { Kernel kernel1 = Kernel.builder() .withAIService(ChatCompletionService.class, - ChatCompletionService.builder() + OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId("gpt-35-turbo-2") .build()) @@ -42,13 +43,13 @@ public static void main(String[] args) { .credential(new KeyCredential("a-key")) .buildAsyncClient(); - TextGenerationService textGenerationService = TextGenerationService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() + .withModelId("gpt-35-turbo") .withOpenAIAsyncClient(client2) - .withModelId("text-davinci-003") .build(); Kernel kernel2 = Kernel.builder() - .withAIService(TextGenerationService.class, textGenerationService) + .withAIService(ChatCompletionService.class, openAIChatCompletion) .build(); ///////////////////////////////////////////////////////// diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example43_GetModelResult.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example43_GetModelResult.java similarity index 95% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example43_GetModelResult.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example43_GetModelResult.java index 9d2f371c..9dc86a68 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example43_GetModelResult.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example43_GetModelResult.java @@ -3,6 +3,7 @@ import com.azure.ai.openai.OpenAIAsyncClient; import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.ai.openai.models.CompletionsUsage; import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; @@ -68,9 +69,9 @@ public static void main(String[] args) { // Display results System.out.println(result.getResult()); System.out.println( - "Usage: " + result + "Usage: " + ((CompletionsUsage) result .getMetadata() - .getUsage().getTotalTokens()); + .getUsage()).getTotalTokens()); System.out.println(); } } diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example49_LogitBias.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example49_LogitBias.java similarity index 96% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example49_LogitBias.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example49_LogitBias.java index 6dcda1c2..e3a8ce22 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example49_LogitBias.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example49_LogitBias.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.orchestration.InvocationContext; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; @@ -43,7 +44,7 @@ public static void main(String[] args) { .buildAsyncClient(); } - ChatCompletionService openAIChatCompletion = ChatCompletionService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId("gpt-35-turbo-2") .build(); diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example55_TextChunker.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example55_TextChunker.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example55_TextChunker.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example55_TextChunker.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example57_KernelHooks.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example57_KernelHooks.java similarity index 98% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example57_KernelHooks.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example57_KernelHooks.java index ce5ae6cd..0203f66c 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example57_KernelHooks.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example57_KernelHooks.java @@ -6,6 +6,7 @@ import com.azure.ai.openai.models.ChatCompletionsOptions; import com.azure.ai.openai.models.ChatRequestMessage; import com.azure.ai.openai.models.ChatRequestSystemMessage; +import com.azure.ai.openai.models.CompletionsUsage; import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; @@ -111,11 +112,12 @@ private static void getUsageAsync(Kernel kernel) { FunctionInvokedHook postExecutionHandler = event -> { System.out.println( - event.getFunction().getName() + " : Post Execution Handler - Usage: " + event - .getResult() - .getMetadata() - .getUsage() - .getTotalTokens()); + event.getFunction().getName() + " : Post Execution Handler - Usage: " + + ((CompletionsUsage) event + .getResult() + .getMetadata() + .getUsage()) + .getTotalTokens()); return event; }; diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example61_MultipleLLMs.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example61_MultipleLLMs.java similarity index 97% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example61_MultipleLLMs.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example61_MultipleLLMs.java index 3ab911b7..8348ee51 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example61_MultipleLLMs.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example61_MultipleLLMs.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import com.microsoft.semantickernel.semanticfunctions.KernelFunction; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; @@ -42,7 +43,7 @@ public static void main(String[] args) { } System.out.println("======== Using Chat GPT model for text generation ========"); - var openAIChatCompletion = ChatCompletionService.builder() + var openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withServiceId("AzureOpenAIChat") .withModelId("gpt-35-turbo-2") diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example62_CustomAIServiceSelector.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example62_CustomAIServiceSelector.java similarity index 96% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example62_CustomAIServiceSelector.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example62_CustomAIServiceSelector.java index be296705..afa5b2e3 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example62_CustomAIServiceSelector.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example62_CustomAIServiceSelector.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import com.microsoft.semantickernel.semanticfunctions.KernelFunction; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; @@ -46,7 +47,7 @@ public static void main(String[] args) { // Build a kernel with multiple chat completion services - var openAIChatCompletion = ChatCompletionService.builder() + var openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withServiceId("AzureOpenAIChat") .withModelId("gpt-35-turbo-2") diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example67_KernelStreaming.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example67_KernelStreaming.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example67_KernelStreaming.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example67_KernelStreaming.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example69_MutableKernelPlugin.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example69_MutableKernelPlugin.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example69_MutableKernelPlugin.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example69_MutableKernelPlugin.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/RunAll.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/RunAll.java similarity index 86% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/RunAll.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/RunAll.java index 3a4d3726..a5b19fe8 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/RunAll.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/RunAll.java @@ -14,7 +14,10 @@ import com.microsoft.semantickernel.samples.syntaxexamples.functions.Example05_InlineFunctionDefinition; import com.microsoft.semantickernel.samples.syntaxexamples.functions.Example09_FunctionTypes; import com.microsoft.semantickernel.samples.syntaxexamples.functions.Example27_PromptFunctionsUsingChatGPT; +import com.microsoft.semantickernel.samples.syntaxexamples.functions.Example59_OpenAIFunctionCalling; import com.microsoft.semantickernel.samples.syntaxexamples.functions.Example60_AdvancedMethodFunctions; +import com.microsoft.semantickernel.samples.syntaxexamples.java.KernelFunctionYaml_Example; +import com.microsoft.semantickernel.samples.syntaxexamples.memory.AzureAISearch_DataStorage; import com.microsoft.semantickernel.samples.syntaxexamples.plugins.Example10_DescribeAllPluginsAndFunctions; import com.microsoft.semantickernel.samples.syntaxexamples.plugins.Example13_ConversationSummaryPlugin; import com.microsoft.semantickernel.samples.syntaxexamples.template.Example06_TemplateLanguage; @@ -28,13 +31,14 @@ * Run all the syntax examples. *

* Refer to the + * "https://github.com/microsoft/semantic-kernel-java/blob/main/samples/semantickernel-concepts/semantickernel-syntax-examples/README.md"> * README for configuring your environment to run the examples. */ public class RunAll { public static void main(String[] args) { List mains = Arrays.asList( + AzureAISearch_DataStorage::main, Example01_NativeFunctions::main, Example03_Arguments::main, Example05_InlineFunctionDefinition::main, @@ -42,15 +46,13 @@ public static void main(String[] args) { Example08_RetryHandler::main, Example09_FunctionTypes::main, Example10_DescribeAllPluginsAndFunctions::main, - //Example11_WebSearchQueries::main, Example13_ConversationSummaryPlugin::main, Example17_ChatGPT::main, - //Example26_AADAuth::main, - Example27_PromptFunctionsUsingChatGPT::main, Example30_ChatWithPrompts::main, Example33_Chat::main, Example41_HttpClientUsage::main, + Example42_KernelBuilder::main, Example43_GetModelResult::main, Example44_MultiChatCompletion::main, Example49_LogitBias::main, @@ -58,12 +60,13 @@ public static void main(String[] args) { Example56_TemplateMethodFunctionsWithMultipleArguments::main, Example57_KernelHooks::main, Example58_ConfigureExecutionSettings::main, + Example59_OpenAIFunctionCalling::main, Example60_AdvancedMethodFunctions::main, - Example61_MultipleLLMs::main, Example62_CustomAIServiceSelector::main, Example63_ChatCompletionPrompts::main, Example64_MultiplePromptTemplates::main, - Example69_MutableKernelPlugin::main); + Example69_MutableKernelPlugin::main, + KernelFunctionYaml_Example::main); Scanner scanner = new Scanner(System.in); mains.forEach(mainMethod -> { diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/audio/Example82_Audio.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/audio/Example82_Audio.java similarity index 97% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/audio/Example82_Audio.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/audio/Example82_Audio.java index e41a9a97..beeebd08 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/audio/Example82_Audio.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/audio/Example82_Audio.java @@ -22,8 +22,6 @@ public class Example82_Audio { // Only required if AZURE_CLIENT_KEY is set private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); - private static final String MODEL_ID = System.getenv() - .getOrDefault("MODEL_ID", "gpt-35-turbo"); private static final String TextToAudioModel = "tts-1"; private static final String AudioToTextModel = "whisper-1"; diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example17_ChatGPT.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example17_ChatGPT.java similarity index 96% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example17_ChatGPT.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example17_ChatGPT.java index 6bdcd6e6..afd68fa7 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example17_ChatGPT.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example17_ChatGPT.java @@ -5,6 +5,8 @@ import com.azure.ai.openai.OpenAIClientBuilder; import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; +import com.azure.core.util.serializer.JsonSerializerProvider; +import com.azure.core.util.serializer.JsonSerializerProviders; import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; @@ -20,7 +22,6 @@ public class Example17_ChatGPT { .getOrDefault("MODEL_ID", "gpt-35-turbo"); public static void main(String[] args) { - System.out.println("======== Open AI - ChatGPT ========"); OpenAIAsyncClient client; diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example30_ChatWithPrompts.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example30_ChatWithPrompts.java similarity index 97% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example30_ChatWithPrompts.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example30_ChatWithPrompts.java index 3a7b7dcd..1357cc47 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example30_ChatWithPrompts.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example30_ChatWithPrompts.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.implementation.EmbeddedResourceLoader; import com.microsoft.semantickernel.plugin.KernelPlugin; import com.microsoft.semantickernel.plugin.KernelPluginFactory; @@ -46,7 +47,7 @@ public static void main(String[] args) throws FileNotFoundException, ServiceNotF .buildAsyncClient(); } - ChatCompletionService openAIChatCompletion = ChatCompletionService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example33_Chat.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example33_Chat.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example33_Chat.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example33_Chat.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example44_MultiChatCompletion.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example44_MultiChatCompletion.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example44_MultiChatCompletion.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example44_MultiChatCompletion.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example63_ChatCompletionPrompts.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example63_ChatCompletionPrompts.java similarity index 93% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example63_ChatCompletionPrompts.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example63_ChatCompletionPrompts.java index 166f1565..88fa9db0 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example63_ChatCompletionPrompts.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example63_ChatCompletionPrompts.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt; import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import java.util.concurrent.CountDownLatch; @@ -36,7 +37,7 @@ public static void main(String[] args) throws InterruptedException { .buildAsyncClient(); } - ChatCompletionService openAIChatCompletion = ChatCompletionService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example96_GeminiChatCompletion.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example96_GeminiChatCompletion.java new file mode 100644 index 00000000..8b3aac3a --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example96_GeminiChatCompletion.java @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.syntaxexamples.chatcompletion; + +import com.google.cloud.vertexai.VertexAI; +import com.microsoft.semantickernel.aiservices.google.chatcompletion.GeminiChatCompletion; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; + +public class Example96_GeminiChatCompletion { + private static final String PROJECT_ID = System.getenv("PROJECT_ID"); + private static final String LOCATION = System.getenv("LOCATION"); + private static final String MODEL_ID = System.getenv("GEMINI_MODEL_ID"); + + public static void main(String[] args) { + // Authenticate with Google Cloud running: + // gcloud config set project PROJECT_ID + // gcloud auth login ACCOUNT + // + // Or if you want to use an API key follow: + // https://cloud.google.com/docs/authentication/api-keys#using-with-client-libs + + VertexAI client = new VertexAI(PROJECT_ID, LOCATION); + + ChatCompletionService geminiChat = GeminiChatCompletion.builder() + .withVertexAIClient(client) + .withModelId(MODEL_ID) + .build(); + + System.out.println("Chat content:"); + System.out.println("------------------------"); + + ChatHistory chatHistory = new ChatHistory(); + + // First user message + chatHistory.addUserMessage("Hi, I'm looking for book suggestions"); + messageOutput(chatHistory); + + reply(geminiChat, chatHistory); + messageOutput(chatHistory); + + chatHistory.addUserMessage( + "I love history and philosophy, I'd like to learn something new about Greece, any suggestion"); + messageOutput(chatHistory); + + reply(geminiChat, chatHistory); + messageOutput(chatHistory); + } + + private static void messageOutput(ChatHistory chatHistory) { + var message = chatHistory.getLastMessage().get(); + System.out.println(message.getAuthorRole() + ": " + message.getContent()); + System.out.println("------------------------"); + } + + private static void reply(ChatCompletionService geminiChat, ChatHistory chatHistory) { + var reply = geminiChat.getChatMessageContentsAsync(chatHistory, null, null) + .block(); + + StringBuilder message = new StringBuilder(); + reply.forEach(chatMessageContent -> message.append(chatMessageContent.getContent())); + chatHistory.addAssistantMessage(message.toString()); + } +} diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example_ChatWithVision.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example_ChatWithVision.java new file mode 100644 index 00000000..2c1acbc9 --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/Example_ChatWithVision.java @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.syntaxexamples.chatcompletion; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.credential.KeyCredential; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; +import com.microsoft.semantickernel.implementation.CollectionUtil; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageImageContent; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +public class Example_ChatWithVision { + + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); + + // Only required if AZURE_CLIENT_KEY is set + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + + // NOTE THAT vision is GPT4 only + private static final String MODEL_ID = System.getenv() + .getOrDefault("MODEL_ID", "gpt-4o"); + + public static void main(String[] args) throws MalformedURLException { + System.out.println("======== Open AI - Chat with Vision ========"); + + OpenAIAsyncClient client; + + if (AZURE_CLIENT_KEY != null) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(AZURE_CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + ChatCompletionService chatGPT = OpenAIChatCompletion.builder() + .withModelId(MODEL_ID) + .withOpenAIAsyncClient(client) + .build(); + + describeUrl(chatGPT); + describeImage(chatGPT); + } + + private static void describeImage(ChatCompletionService chatGPT) throws MalformedURLException { + + try (InputStream duke = Example_ChatWithVision.class.getResourceAsStream("duke.png")) { + byte[] image = duke.readAllBytes(); + + ChatHistory chatHistory = new ChatHistory( + "You look at images and answer questions about them"); + + // First user message + chatHistory.addUserMessage( + "This image is a cartoon drawing of the Java Duke character riding a dinosaur. What type of dinosaur is it?"); + chatHistory.addMessage( + ChatMessageImageContent.builder() + .withImage("png", image) + .build()); + + var reply = chatGPT.getChatMessageContentsAsync(chatHistory, null, null); + + String message = reply + .mapNotNull(CollectionUtil::getLastOrNull) + .map(ChatMessageContent::getContent) + .block(); + + System.out.println("\n------------------------"); + System.out.print(message); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void describeUrl(ChatCompletionService chatGPT) throws MalformedURLException { + ChatHistory chatHistory = new ChatHistory("You look at images and describe them"); + + // First user message + chatHistory.addUserMessage("Describe the following image"); + chatHistory.addMessage( + ChatMessageImageContent.builder() + .withImageUrl(new URL("https://cr.openjdk.org/~jeff/Duke/jpg/Welcome.jpg")) + .build()); + + var reply = chatGPT.getChatMessageContentsAsync(chatHistory, null, null); + + String message = reply + .mapNotNull(CollectionUtil::getLastOrNull) + .map(ChatMessageContent::getContent) + .block(); + + System.out.println("\n------------------------"); + System.out.print(message); + } + +} diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example08_RetryHandler.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example08_RetryHandler.java similarity index 84% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example08_RetryHandler.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example08_RetryHandler.java index 37a490e6..508d3db4 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example08_RetryHandler.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example08_RetryHandler.java @@ -7,16 +7,18 @@ import com.azure.core.http.policy.ExponentialBackoffOptions; import com.azure.core.http.policy.RetryOptions; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.exceptions.ConfigurationException; import com.microsoft.semantickernel.semanticfunctions.KernelFunction; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; import java.time.Duration; public class Example08_RetryHandler { private static final String MODEL_ID = System.getenv() - .getOrDefault("MODEL_ID", "text-davinci-003"); + .getOrDefault("MODEL_ID", "gpt-35-turbo"); public static void main(String[] args) throws ConfigurationException { // Create a Kernel with the HttpClient @@ -31,13 +33,13 @@ public static void main(String[] args) throws ConfigurationException { .credential(new AzureKeyCredential("BAD KEY")) .buildAsyncClient(); - TextGenerationService textGenerationService = TextGenerationService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); Kernel kernel = Kernel.builder() - .withAIService(TextGenerationService.class, textGenerationService) + .withAIService(ChatCompletionService.class, openAIChatCompletion) .build(); String question = "How popular is the Polly library?"; diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example26_AADAuth.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example26_AADAuth.java similarity index 91% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example26_AADAuth.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example26_AADAuth.java index 34662657..5e885795 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example26_AADAuth.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example26_AADAuth.java @@ -6,6 +6,7 @@ import com.azure.identity.DeviceCodeCredential; import com.azure.identity.DeviceCodeCredentialBuilder; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.exceptions.ConfigurationException; import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; @@ -26,7 +27,7 @@ public static void main(String[] args) throws ConfigurationException { .endpoint(CLIENT_ENDPOINT) .buildAsyncClient(); - ChatCompletionService chatService = ChatCompletionService.builder() + ChatCompletionService chatService = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example41_HttpClientUsage.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example41_HttpClientUsage.java similarity index 84% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example41_HttpClientUsage.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example41_HttpClientUsage.java index bc7c218c..763f0a6c 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example41_HttpClientUsage.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example41_HttpClientUsage.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.http.HttpClient; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; @@ -27,7 +28,7 @@ private static void useDefaultHttpClient() { var kernel = Kernel.builder() .withAIService(ChatCompletionService.class, - ChatCompletionService.builder() + OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId("gpt-35-turbo-2") .build()) @@ -46,13 +47,13 @@ private static void useCustomHttpClient() { .credential(new AzureKeyCredential("BAD KEY")) .buildAsyncClient(); - TextGenerationService textGenerationService = TextGenerationService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) - .withModelId("text-davinci-003") + .withModelId("gpt-35-turbo") .build(); Kernel kernel = Kernel.builder() - .withAIService(TextGenerationService.class, textGenerationService) + .withAIService(ChatCompletionService.class, openAIChatCompletion) .build(); } } diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example58_ConfigureExecutionSettings.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example58_ConfigureExecutionSettings.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example58_ConfigureExecutionSettings.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/configuration/Example58_ConfigureExecutionSettings.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example01_NativeFunctions.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example01_NativeFunctions.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example01_NativeFunctions.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example01_NativeFunctions.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example03_Arguments.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example03_Arguments.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example03_Arguments.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example03_Arguments.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example05_InlineFunctionDefinition.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example05_InlineFunctionDefinition.java similarity index 91% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example05_InlineFunctionDefinition.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example05_InlineFunctionDefinition.java index 7aa6e18b..7ecf9a0d 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example05_InlineFunctionDefinition.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example05_InlineFunctionDefinition.java @@ -6,12 +6,14 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.exceptions.ConfigurationException; import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import com.microsoft.semantickernel.semanticfunctions.KernelFunction; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; import java.time.Instant; import java.time.ZoneOffset; @@ -25,7 +27,7 @@ public class Example05_InlineFunctionDefinition { // Only required if AZURE_CLIENT_KEY is set private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); private static final String MODEL_ID = System.getenv() - .getOrDefault("MODEL_ID", "text-davinci-003"); + .getOrDefault("MODEL_ID", "gpt-35-turbo"); public static void main(String[] args) throws ConfigurationException { @@ -42,13 +44,13 @@ public static void main(String[] args) throws ConfigurationException { .buildAsyncClient(); } - TextGenerationService textGenerationService = TextGenerationService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); Kernel kernel = Kernel.builder() - .withAIService(TextGenerationService.class, textGenerationService) + .withAIService(ChatCompletionService.class, openAIChatCompletion) .build(); System.out.println("======== Inline Function Definition ========"); diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example09_FunctionTypes.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example09_FunctionTypes.java similarity index 98% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example09_FunctionTypes.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example09_FunctionTypes.java index b10900d1..c6c1fdff 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example09_FunctionTypes.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example09_FunctionTypes.java @@ -8,6 +8,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.aiservices.openai.textcompletion.OpenAITextGenerationService; import com.microsoft.semantickernel.contextvariables.ContextVariable; import com.microsoft.semantickernel.contextvariables.ContextVariableType; @@ -19,6 +20,7 @@ import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; import java.nio.file.Path; import java.time.Instant; @@ -42,7 +44,7 @@ public class Example09_FunctionTypes { // Only required if AZURE_CLIENT_KEY is set private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); private static final String MODEL_ID = System.getenv() - .getOrDefault("MODEL_ID", "text-davinci-003"); + .getOrDefault("MODEL_ID", "gpt-35-turbo"); public static void main(String[] args) throws InterruptedException { @@ -61,7 +63,7 @@ public static void main(String[] args) throws InterruptedException { .buildAsyncClient(); } - TextGenerationService textGenerationService = OpenAITextGenerationService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); @@ -73,7 +75,7 @@ public static void main(String[] args) throws InterruptedException { KernelPlugin summarize = KernelPluginFactory .importPluginFromDirectory( Path.of(PLUGIN_DIR, - "java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins"), + "samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins"), "SummarizePlugin", null); @@ -86,7 +88,7 @@ public static void main(String[] args) throws InterruptedException { Example09_FunctionTypes.class); Kernel kernel = Kernel.builder() - .withAIService(TextGenerationService.class, textGenerationService) + .withAIService(ChatCompletionService.class, openAIChatCompletion) .withPlugin(plugin) .withPlugin(summarize) .withPlugin(examplePlugin) diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example27_PromptFunctionsUsingChatGPT.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example27_PromptFunctionsUsingChatGPT.java similarity index 93% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example27_PromptFunctionsUsingChatGPT.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example27_PromptFunctionsUsingChatGPT.java index 0dae3c7e..f72ec237 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example27_PromptFunctionsUsingChatGPT.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example27_PromptFunctionsUsingChatGPT.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.contextvariables.ContextVariableTypes; import com.microsoft.semantickernel.semanticfunctions.KernelFunction; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; @@ -38,7 +39,7 @@ public static void main(String[] args) { } System.out.println("======== Using Chat GPT model for text generation ========"); - ChatCompletionService openAIChatCompletion = ChatCompletionService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example59_OpenAIFunctionCalling.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example59_OpenAIFunctionCalling.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example59_OpenAIFunctionCalling.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example59_OpenAIFunctionCalling.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example60_AdvancedMethodFunctions.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example60_AdvancedMethodFunctions.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example60_AdvancedMethodFunctions.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example60_AdvancedMethodFunctions.java diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example98_GeminiFunctionCalling.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example98_GeminiFunctionCalling.java new file mode 100644 index 00000000..0dd425f6 --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/functions/Example98_GeminiFunctionCalling.java @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.syntaxexamples.functions; + +import com.google.cloud.vertexai.VertexAI; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.google.chatcompletion.GeminiChatCompletion; +import com.microsoft.semantickernel.aiservices.google.chatcompletion.GeminiChatMessageContent; +import com.microsoft.semantickernel.aiservices.google.chatcompletion.GeminiFunctionCall; +import com.microsoft.semantickernel.contextvariables.ContextVariableTypes; +import com.microsoft.semantickernel.orchestration.FunctionResult; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.orchestration.InvocationReturnMode; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.orchestration.ToolCallBehavior; +import com.microsoft.semantickernel.plugin.KernelPluginFactory; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt; +import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; +import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class Example98_GeminiFunctionCalling { + private static final String PROJECT_ID = System.getenv("PROJECT_ID"); + private static final String LOCATION = System.getenv("LOCATION"); + private static final String MODEL_ID = System.getenv("GEMINI_MODEL_ID"); + + // Define functions that can be called by the model + public static class HelperFunctions { + + @DefineKernelFunction(name = "currentUtcTime", description = "Retrieves the current time in UTC.") + public String currentUtcTime() { + return ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME); + } + + @DefineKernelFunction(name = "getWeatherForCity", description = "Gets the current weather for the specified city") + public String getWeatherForCity( + @KernelFunctionParameter(name = "cityName", description = "Name of the city") String cityName) { + switch (cityName) { + case "Thrapston": + return "80 and sunny"; + case "Boston": + return "61 and rainy"; + case "London": + return "55 and cloudy"; + case "Miami": + return "80 and sunny"; + case "Paris": + return "60 and rainy"; + case "Tokyo": + return "50 and sunny"; + case "Sydney": + return "75 and sunny"; + case "Tel Aviv": + return "80 and sunny"; + default: + return "31 and snowing"; + } + } + + public static void main(String[] args) throws NoSuchMethodException { + System.out.println("======== Gemini - Function calling ========"); + + VertexAI client = new VertexAI(PROJECT_ID, LOCATION); + + ChatCompletionService chat = GeminiChatCompletion.builder() + .withModelId(MODEL_ID) + .withVertexAIClient(client) + .build(); + + var plugin = KernelPluginFactory.createFromObject(new HelperFunctions(), + "HelperFunctions"); + + var kernel = Kernel.builder() + .withAIService(ChatCompletionService.class, chat) + .withPlugin(plugin) + .build(); + + System.out.println("======== Example 1: Use automated function calling ========"); + + var function = KernelFunctionFromPrompt.builder() + .withTemplate( + "Given the current time of day and weather, what is the likely color of the sky in Boston?") + .withDefaultExecutionSettings( + PromptExecutionSettings.builder() + .withTemperature(0.4) + .withTopP(1) + .withMaxTokens(100) + .build()) + .build(); + + var result = kernel + .invokeAsync(function) + .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true)) + .withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(String.class)) + .block(); + + System.out.println(result.getResult()); + + System.out.println("======== Example 2: Use manual function calling ========"); + + var chatHistory = new ChatHistory(); + chatHistory.addUserMessage( + "Given the current time of day and weather, what is the likely color of the sky in Boston?"); + + while (true) { + var message = (GeminiChatMessageContent) chat.getChatMessageContentsAsync( + chatHistory, + kernel, + InvocationContext.builder() + .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(false)) + .withReturnMode(InvocationReturnMode.LAST_MESSAGE_ONLY) + .build()) + .block().get(0); + + // Add the assistant's response to the chat history + chatHistory.addMessage(message); + if (message.getContent() != null && !message.getContent().isEmpty()) { + System.out.println(message.getContent()); + } + + // Process the functions calls or break if there are no more functions to call + if (message.getGeminiFunctionCalls().isEmpty()) { + break; + } + + List functionResponses = new ArrayList<>(); + for (var geminiFunction : message.getGeminiFunctionCalls()) { + + String content = null; + try { + // getFunction will throw an exception if the function is not found + var fn = kernel.getFunction(geminiFunction.getPluginName(), + geminiFunction.getFunctionName()); + + var arguments = KernelFunctionArguments.builder(); + geminiFunction.getFunctionCall().getArgs().getFieldsMap() + .forEach((key, value) -> { + arguments.withVariable(key, value.getStringValue()); + }); + + // Invoke the function and add the result to the list of function responses + FunctionResult functionResult = fn + .invokeAsync(kernel, arguments.build(), null, null).block(); + + functionResponses.add(new GeminiFunctionCall( + geminiFunction.getFunctionCall(), functionResult)); + } catch (IllegalArgumentException e) { + content = "Unable to find function. Please try again!"; + } + } + + // Add the function responses to the chat history + ChatMessageContent functionResponsesMessage = new GeminiChatMessageContent<>( + AuthorRole.USER, + "", null, null, null, null, functionResponses); + + chatHistory.addMessage(functionResponsesMessage); + } + } + } +} diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/FunctionsHandlebars_Example.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/FunctionsHandlebars_Example.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/FunctionsHandlebars_Example.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/FunctionsHandlebars_Example.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/FunctionsWithinPrompts_Example.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/FunctionsWithinPrompts_Example.java similarity index 89% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/FunctionsWithinPrompts_Example.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/FunctionsWithinPrompts_Example.java index 4b0cb3c7..ceb9983e 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/FunctionsWithinPrompts_Example.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/FunctionsWithinPrompts_Example.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.exceptions.ConfigurationException; import com.microsoft.semantickernel.orchestration.ToolCallBehavior; import com.microsoft.semantickernel.plugin.KernelPlugin; @@ -13,10 +14,9 @@ import com.microsoft.semantickernel.samples.plugins.ConversationSummaryPlugin; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt; -import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; -import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageTextContent; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; @@ -51,7 +51,7 @@ public static void main(String[] args) throws ConfigurationException, IOExceptio .buildAsyncClient(); } - ChatCompletionService chatCompletionService = ChatCompletionService.builder() + ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); @@ -70,18 +70,16 @@ public static void main(String[] args) throws ConfigurationException, IOExceptio List fewShotExamples = Arrays.asList( new ChatHistory( Arrays.asList( - new ChatMessageContent(AuthorRole.USER, + ChatMessageTextContent.userMessage( "Can you send a very quick approval to the marketing team?"), - new ChatMessageContent(AuthorRole.SYSTEM, "Intent:"), - new ChatMessageContent(AuthorRole.ASSISTANT, - "ContinueConversation"))), + ChatMessageTextContent.systemMessage("Intent:"), + ChatMessageTextContent.assistantMessage("ContinueConversation"))), new ChatHistory( Arrays.asList( - new ChatMessageContent(AuthorRole.USER, - "Thats all"), - new ChatMessageContent(AuthorRole.SYSTEM, "Intent:"), - new ChatMessageContent(AuthorRole.ASSISTANT, "EndConversation")))); + ChatMessageTextContent.userMessage("Thats all"), + ChatMessageTextContent.systemMessage("Intent:"), + ChatMessageTextContent.assistantMessage("EndConversation")))); // Create handlebars template for intent // @@ -135,12 +133,12 @@ public static void main(String[] args) throws ConfigurationException, IOExceptio /* * Renders to: - * + * * Instructions: What is the intent of this request? * Do not explain the reasoning, just reply back with the intent. If you are unsure, * reply with . * Choices: ContinueConversation,EndConversation. - * + * * Can you send a very quick approval to the marketing * team? * Intent: @@ -148,7 +146,7 @@ public static void main(String[] args) throws ConfigurationException, IOExceptio * Can you send the full update to the marketing team? * Intent: * EndConversation - * + * * Can you send an approval to the marketing team? * Intent: */ diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/KernelFunctionYaml_Example.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/KernelFunctionYaml_Example.java similarity index 89% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/KernelFunctionYaml_Example.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/KernelFunctionYaml_Example.java index 05e12e10..900513c8 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/KernelFunctionYaml_Example.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/java/KernelFunctionYaml_Example.java @@ -7,12 +7,14 @@ import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.Kernel.Builder; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.exceptions.ConfigurationException; import com.microsoft.semantickernel.implementation.EmbeddedResourceLoader; import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.semanticfunctions.KernelFunction; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionYaml; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; import java.io.IOException; @@ -24,7 +26,7 @@ public class KernelFunctionYaml_Example { // Only required if AZURE_CLIENT_KEY is set private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); private static final String MODEL_ID = System.getenv() - .getOrDefault("MODEL_ID", "text-davinci-003"); + .getOrDefault("MODEL_ID", "gpt-35-turbo"); public static void main(String[] args) throws ConfigurationException, IOException { @@ -41,13 +43,13 @@ public static void main(String[] args) throws ConfigurationException, IOExceptio .buildAsyncClient(); } - TextGenerationService textGenerationService = TextGenerationService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); Builder kernelBuilder = Kernel.builder() - .withAIService(TextGenerationService.class, textGenerationService); + .withAIService(ChatCompletionService.class, openAIChatCompletion); semanticKernelTemplate(kernelBuilder.build()); handlebarsTemplate(kernelBuilder.build()); diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/memory/AzureAISearch_DataStorage.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/memory/AzureAISearch_DataStorage.java new file mode 100644 index 00000000..92316385 --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/memory/AzureAISearch_DataStorage.java @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.syntaxexamples.memory; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.credential.KeyCredential; +import com.azure.core.util.ClientOptions; +import com.azure.core.util.MetricsOptions; +import com.azure.core.util.TracingOptions; +import com.azure.search.documents.indexes.SearchIndexAsyncClient; +import com.azure.search.documents.indexes.SearchIndexClientBuilder; +import com.microsoft.semantickernel.aiservices.openai.textembedding.OpenAITextEmbeddingGenerationService; +import com.microsoft.semantickernel.connectors.data.azureaisearch.AzureAISearchVectorStore; +import com.microsoft.semantickernel.connectors.data.azureaisearch.AzureAISearchVectorStoreOptions; +import com.microsoft.semantickernel.connectors.data.azureaisearch.AzureAISearchVectorStoreRecordCollection; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordDataAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordKeyAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordVectorAttribute; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class AzureAISearch_DataStorage { + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); + + // Only required if AZURE_CLIENT_KEY is set + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + + ////////////////////////////////////////////////////////////// + // Azure AI Search configuration + ////////////////////////////////////////////////////////////// + private static final String AZURE_AI_SEARCH_ENDPOINT = System.getenv("AZURE_AISEARCH_ENDPOINT"); + private static final String AZURE_AISEARCH_KEY = System.getenv("AZURE_AISEARCH_KEY"); + private static final String MODEL_ID = System.getenv() + .getOrDefault("EMBEDDING_MODEL_ID", "text-embedding-3-large"); + private static final int EMBEDDING_DIMENSIONS = 1536; + + static class GitHubFile { + @VectorStoreRecordKeyAttribute() + private final String id; + @VectorStoreRecordDataAttribute(hasEmbedding = true, embeddingFieldName = "embedding") + private final String description; + @VectorStoreRecordDataAttribute + private final String link; + @VectorStoreRecordVectorAttribute(dimensions = EMBEDDING_DIMENSIONS, indexKind = "Hnsw") + private final List embedding; + + public GitHubFile() { + this(null, null, null, Collections.emptyList()); + } + + public GitHubFile( + String id, + String description, + String link, + List embedding) { + this.id = id; + this.description = description; + this.link = link; + this.embedding = embedding; + } + + static String encodeId(String realId) { + byte[] bytes = Base64.getUrlEncoder().encode(realId.getBytes(StandardCharsets.UTF_8)); + return new String(bytes, StandardCharsets.UTF_8); + } + } + + public static void main(String[] args) { + System.out.println("=============================================================="); + System.out.println("========== Azure AI Search Vector Store Example =============="); + System.out.println("=============================================================="); + + OpenAIAsyncClient client; + + if (AZURE_CLIENT_KEY != null) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(AZURE_CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + var embeddingGeneration = OpenAITextEmbeddingGenerationService.builder() + .withOpenAIAsyncClient(client) + .withModelId(MODEL_ID) + .withDimensions(EMBEDDING_DIMENSIONS) + .build(); + + var searchClient = new SearchIndexClientBuilder() + .endpoint(AZURE_AI_SEARCH_ENDPOINT) + .credential(new AzureKeyCredential(AZURE_AISEARCH_KEY)) + .clientOptions(clientOptions()) + .buildAsyncClient(); + + dataStorageWithAzureAISearch(searchClient, embeddingGeneration); + } + + public static void dataStorageWithAzureAISearch( + SearchIndexAsyncClient searchClient, + OpenAITextEmbeddingGenerationService embeddingGeneration) { + + // Create a new Azure AI Search vector store + var azureAISearchVectorStore = AzureAISearchVectorStore.builder() + .withClient(searchClient) + .withOptions(new AzureAISearchVectorStoreOptions()) + .build(); + + String collectionName = "skgithubfiles"; + var collection = azureAISearchVectorStore.getCollection(collectionName, GitHubFile.class, + null); + + // Create collection if it does not exist and store data + collection + .createCollectionIfNotExistsAsync() + .then(storeData(collection, embeddingGeneration, sampleData())) + .block(); + + // Query the Azure AI Search client for results + // This might take a few seconds to return the best result + var result = searchClient.getSearchAsyncClient(collectionName) + .search("How to get started with the Semantic Kernel?") + .blockFirst(); + + GitHubFile gitHubFile = result.getDocument(GitHubFile.class); + System.out.println("Best result: " + gitHubFile.description + ". Link: " + gitHubFile.link); + } + + private static Mono> storeData( + AzureAISearchVectorStoreRecordCollection recordStore, + OpenAITextEmbeddingGenerationService embeddingGeneration, + Map data) { + + return Flux.fromIterable(data.entrySet()) + .flatMap(entry -> { + System.out.println("Save '" + entry.getKey() + "' to memory."); + + return embeddingGeneration + .generateEmbeddingsAsync(Collections.singletonList(entry.getValue())) + .flatMap(embeddings -> { + GitHubFile gitHubFile = new GitHubFile( + GitHubFile.encodeId(entry.getKey()), + entry.getValue(), + entry.getKey(), + embeddings.get(0).getVector()); + return recordStore.upsertAsync(gitHubFile, null); + }); + }) + .collectList(); + } + + private static Map sampleData() { + return Arrays.stream(new String[][] { + { "https://github.com/microsoft/semantic-kernel/blob/main/README.md", + "README: Installation, getting started with Semantic Kernel, and how to contribute" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/02-running-prompts-from-file.ipynb", + "Jupyter notebook describing how to pass prompts from a file to a semantic skill or function" }, + { "https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT", + "Sample demonstrating how to create a chat skill interfacing with ChatGPT" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/VolatileMemoryStore.cs", + "C# class that defines a volatile embedding store" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/samples/dotnet/KernelHttpServer/README.md", + "README: How to set up a Semantic Kernel Service API using Azure Function Runtime v4" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/samples/apps/chat-summary-webapp-react/README.md", + "README: README associated with a sample chat summary react-based webapp" }, + }).collect(Collectors.toMap(element -> element[0], element -> element[1])); + } + + private static ClientOptions clientOptions() { + return new ClientOptions() + .setTracingOptions(new TracingOptions()) + .setMetricsOptions(new MetricsOptions()) + .setApplicationId("Semantic-Kernel"); + } +} diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/memory/InMemory_DataStorage.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/memory/InMemory_DataStorage.java new file mode 100644 index 00000000..b189081b --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/memory/InMemory_DataStorage.java @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.syntaxexamples.memory; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.credential.KeyCredential; +import com.azure.core.util.ClientOptions; +import com.azure.core.util.MetricsOptions; +import com.azure.core.util.TracingOptions; +import com.azure.search.documents.indexes.SearchIndexAsyncClient; +import com.azure.search.documents.indexes.SearchIndexClientBuilder; +import com.microsoft.semantickernel.aiservices.openai.textembedding.OpenAITextEmbeddingGenerationService; +import com.microsoft.semantickernel.connectors.data.azureaisearch.AzureAISearchVectorStore; +import com.microsoft.semantickernel.connectors.data.azureaisearch.AzureAISearchVectorStoreOptions; +import com.microsoft.semantickernel.connectors.data.azureaisearch.AzureAISearchVectorStoreRecordCollection; +import com.microsoft.semantickernel.data.VolatileVectorStore; +import com.microsoft.semantickernel.data.VolatileVectorStoreRecordCollection; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordDataAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordKeyAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordVectorAttribute; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class InMemory_DataStorage { + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); + + // Only required if AZURE_CLIENT_KEY is set + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + + // Embedding model configuration + private static final String MODEL_ID = System.getenv() + .getOrDefault("EMBEDDING_MODEL_ID", "text-embedding-3-large"); + private static final int EMBEDDING_DIMENSIONS = 1536; + + static class GitHubFile { + @VectorStoreRecordKeyAttribute() + private final String id; + @VectorStoreRecordDataAttribute(hasEmbedding = true, embeddingFieldName = "embedding") + private final String description; + @VectorStoreRecordDataAttribute + private final String link; + @VectorStoreRecordVectorAttribute(dimensions = EMBEDDING_DIMENSIONS, indexKind = "Hnsw") + private final List embedding; + + public GitHubFile( + String id, + String description, + String link, + List embedding) { + this.id = id; + this.description = description; + this.link = link; + this.embedding = embedding; + } + + public String getId() { + return id; + } + + public String getDescription() { + return description; + } + + static String encodeId(String realId) { + byte[] bytes = Base64.getUrlEncoder().encode(realId.getBytes(StandardCharsets.UTF_8)); + return new String(bytes, StandardCharsets.UTF_8); + } + } + + public static void main(String[] args) { + System.out.println("==================================================================="); + System.out.println("========== Volatile (In memory) Vector Store Example =============="); + System.out.println("==================================================================="); + + OpenAIAsyncClient client; + + if (AZURE_CLIENT_KEY != null) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(AZURE_CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + var embeddingGeneration = OpenAITextEmbeddingGenerationService.builder() + .withOpenAIAsyncClient(client) + .withModelId(MODEL_ID) + .withDimensions(EMBEDDING_DIMENSIONS) + .build(); + + inMemoryDataStorage(embeddingGeneration); + } + + public static void inMemoryDataStorage(OpenAITextEmbeddingGenerationService embeddingGeneration) { + // Create a new Volatile vector store + var volatileVectorStore = new VolatileVectorStore(); + + String collectionName = "skgithubfiles"; + var collection = volatileVectorStore.getCollection(collectionName, GitHubFile.class, null); + + // Create collection if it does not exist and store data + List ids = collection + .createCollectionIfNotExistsAsync() + .then(storeData(collection, embeddingGeneration, sampleData())) + .block(); + + // Retrieve all records from the collection + List data = collection.getBatchAsync(ids, null).block(); + + data.forEach(gitHubFile -> System.out.println("Retrieved: " + gitHubFile.getDescription())); + } + + private static Mono> storeData( + VolatileVectorStoreRecordCollection recordCollection, + OpenAITextEmbeddingGenerationService embeddingGeneration, + Map data) { + + return Flux.fromIterable(data.entrySet()) + .flatMap(entry -> { + System.out.println("Save '" + entry.getKey() + "' to memory."); + + return embeddingGeneration + .generateEmbeddingsAsync(Collections.singletonList(entry.getValue())) + .flatMap(embeddings -> { + GitHubFile gitHubFile = new GitHubFile( + GitHubFile.encodeId(entry.getKey()), + entry.getValue(), + entry.getKey(), + embeddings.get(0).getVector()); + return recordCollection.upsertAsync(gitHubFile, null); + }); + }) + .collectList(); + } + + private static Map sampleData() { + return Arrays.stream(new String[][] { + { "https://github.com/microsoft/semantic-kernel/blob/main/README.md", + "README: Installation, getting started with Semantic Kernel, and how to contribute" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/02-running-prompts-from-file.ipynb", + "Jupyter notebook describing how to pass prompts from a file to a semantic skill or function" }, + { "https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT", + "Sample demonstrating how to create a chat skill interfacing with ChatGPT" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/VolatileMemoryStore.cs", + "C# class that defines a volatile embedding store" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/samples/dotnet/KernelHttpServer/README.md", + "README: How to set up a Semantic Kernel Service API using Azure Function Runtime v4" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/samples/apps/chat-summary-webapp-react/README.md", + "README: README associated with a sample chat summary react-based webapp" }, + }).collect(Collectors.toMap(element -> element[0], element -> element[1])); + } +} diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/memory/Redis_DataStorage.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/memory/Redis_DataStorage.java new file mode 100644 index 00000000..cde31a3a --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/memory/Redis_DataStorage.java @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.syntaxexamples.memory; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.credential.KeyCredential; +import com.azure.core.util.ClientOptions; +import com.azure.core.util.MetricsOptions; +import com.azure.core.util.TracingOptions; +import com.microsoft.semantickernel.aiservices.openai.textembedding.OpenAITextEmbeddingGenerationService; +import com.microsoft.semantickernel.connectors.data.redis.RedisVectorStore; +import com.microsoft.semantickernel.connectors.data.redis.RedisVectorStoreOptions; +import com.microsoft.semantickernel.data.VectorStoreRecordCollection; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordDataAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordKeyAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordVectorAttribute; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import redis.clients.jedis.JedisPooled; + +public class Redis_DataStorage { + + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); + + // Only required if AZURE_CLIENT_KEY is set + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + + private static final String MODEL_ID = System.getenv() + .getOrDefault("EMBEDDING_MODEL_ID", "text-embedding-3-large"); + private static final int EMBEDDING_DIMENSIONS = 1536; + + // Can start a test server with: + // docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest + private static final String REDIS_URL = "redis://127.0.0.1:6379"; + + public static class GitHubFile { + + @VectorStoreRecordKeyAttribute() + private final String id; + @VectorStoreRecordDataAttribute(hasEmbedding = true, embeddingFieldName = "embedding") + private final String description; + @VectorStoreRecordDataAttribute + private final String link; + @VectorStoreRecordVectorAttribute(dimensions = EMBEDDING_DIMENSIONS, indexKind = "Hnsw") + private final List embedding; + + public GitHubFile() { + this(null, null, null, Collections.emptyList()); + } + + public GitHubFile( + String id, + String description, + String link, + List embedding) { + this.id = id; + this.description = description; + this.link = link; + this.embedding = embedding; + } + + public String getId() { + return id; + } + + public String getDescription() { + return description; + } + + static String encodeId(String realId) { + byte[] bytes = Base64.getUrlEncoder().encode(realId.getBytes(StandardCharsets.UTF_8)); + return new String(bytes, StandardCharsets.UTF_8); + } + } + + public static void main(String[] args) { + System.out.println("=============================================================="); + System.out.println("========== Redis Vector Store Example =============="); + System.out.println("=============================================================="); + + OpenAIAsyncClient client; + + if (AZURE_CLIENT_KEY != null) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(AZURE_CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + var embeddingGeneration = OpenAITextEmbeddingGenerationService.builder() + .withOpenAIAsyncClient(client) + .withModelId(MODEL_ID) + .withDimensions(EMBEDDING_DIMENSIONS) + .build(); + + dataStorageWithRedis(embeddingGeneration); + } + + public static void dataStorageWithRedis( + OpenAITextEmbeddingGenerationService embeddingGeneration) { + + JedisPooled jedis = new JedisPooled(REDIS_URL); + + RedisVectorStore vectorStore = RedisVectorStore.builder() + .withClient(jedis) + .withOptions(new RedisVectorStoreOptions()) + .build(); + + String collectionName = "skgithubfiles"; + var collection = vectorStore.getCollection(collectionName, GitHubFile.class, null); + + // Create collection if it does not exist and store data + List ids = collection + .createCollectionIfNotExistsAsync() + .then(storeData(collection, embeddingGeneration, sampleData())) + .block(); + + List data = collection.getBatchAsync(ids, null).block(); + + data.forEach(gitHubFile -> System.out.println("Retrieved: " + gitHubFile.getDescription())); + + // TODO: Implement search functionality using Redis. + + } + + private static Mono> storeData( + VectorStoreRecordCollection recordStore, + OpenAITextEmbeddingGenerationService embeddingGeneration, + Map data) { + + return Flux.fromIterable(data.entrySet()) + .flatMap(entry -> { + System.out.println("Save '" + entry.getKey() + "' to memory."); + + return embeddingGeneration + .generateEmbeddingsAsync(Collections.singletonList(entry.getValue())) + .flatMap(embeddings -> { + GitHubFile gitHubFile = new GitHubFile( + GitHubFile.encodeId(entry.getKey()), + entry.getValue(), + entry.getKey(), + embeddings.get(0).getVector()); + return recordStore.upsertAsync(gitHubFile, null); + }); + }) + .collectList(); + } + + private static Map sampleData() { + return Arrays.stream(new String[][] { + { "https://github.com/microsoft/semantic-kernel/blob/main/README.md", + "README: Installation, getting started with Semantic Kernel, and how to contribute" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/02-running-prompts-from-file.ipynb", + "Jupyter notebook describing how to pass prompts from a file to a semantic skill or function" }, + { "https://github.com/microsoft/semantic-kernel/tree/main/samples/skills/ChatSkill/ChatGPT", + "Sample demonstrating how to create a chat skill interfacing with ChatGPT" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/VolatileMemoryStore.cs", + "C# class that defines a volatile embedding store" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/samples/dotnet/KernelHttpServer/README.md", + "README: How to set up a Semantic Kernel Service API using Azure Function Runtime v4" }, + { "https://github.com/microsoft/semantic-kernel/blob/main/samples/apps/chat-summary-webapp-react/README.md", + "README: README associated with a sample chat summary react-based webapp" }, + }).collect(Collectors.toMap(element -> element[0], element -> element[1])); + } + + private static ClientOptions clientOptions() { + return new ClientOptions() + .setTracingOptions(new TracingOptions()) + .setMetricsOptions(new MetricsOptions()) + .setApplicationId("Semantic-Kernel"); + } +} diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example07_BingAndGooglePlugins.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example07_BingAndGooglePlugins.java similarity index 98% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example07_BingAndGooglePlugins.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example07_BingAndGooglePlugins.java index 6db7deb7..70a8ef28 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example07_BingAndGooglePlugins.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example07_BingAndGooglePlugins.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.samples.connectors.web.bing.BingConnector; import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; @@ -59,7 +60,7 @@ public static void main(String[] args) { var bing = KernelPluginFactory.createFromObject(new WebSearchEnginePlugin(bingConnector), "bing"); - var chatCompletionService = ChatCompletionService.builder() + var chatCompletionService = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example10_DescribeAllPluginsAndFunctions.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example10_DescribeAllPluginsAndFunctions.java similarity index 92% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example10_DescribeAllPluginsAndFunctions.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example10_DescribeAllPluginsAndFunctions.java index 6bc59774..1205b335 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example10_DescribeAllPluginsAndFunctions.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example10_DescribeAllPluginsAndFunctions.java @@ -10,6 +10,7 @@ import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.Kernel.Builder; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.aiservices.openai.textcompletion.OpenAITextGenerationService; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import com.microsoft.semantickernel.plugin.KernelPluginFactory; @@ -19,6 +20,7 @@ import com.microsoft.semantickernel.semanticfunctions.KernelFunctionMetadata; import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; public class Example10_DescribeAllPluginsAndFunctions { @@ -31,7 +33,7 @@ public class Example10_DescribeAllPluginsAndFunctions { // Only required if AZURE_CLIENT_KEY is set private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); private static final String MODEL_ID = System.getenv() - .getOrDefault("MODEL_ID", "text-davinci-003"); + .getOrDefault("MODEL_ID", "gpt-35-turbo"); ///

/// Print a list of all the functions imported into the kernel, including function descriptions, @@ -53,13 +55,13 @@ public static void main(String[] args) { .buildAsyncClient(); } - TextGenerationService textGenerationService = OpenAITextGenerationService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); Builder kernelBuilder = Kernel.builder() - .withAIService(TextGenerationService.class, textGenerationService); + .withAIService(ChatCompletionService.class, openAIChatCompletion); kernelBuilder.withPlugin( KernelPluginFactory.createFromObject( @@ -74,7 +76,7 @@ public static void main(String[] args) { KernelPluginFactory .importPluginFromDirectory( Path.of(PLUGIN_DIR, - "java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins"), + "samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins"), "SummarizePlugin", null)); diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example13_ConversationSummaryPlugin.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example13_ConversationSummaryPlugin.java similarity index 95% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example13_ConversationSummaryPlugin.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example13_ConversationSummaryPlugin.java index eeff25c8..9cdafbb7 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example13_ConversationSummaryPlugin.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/plugins/Example13_ConversationSummaryPlugin.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.aiservices.openai.textcompletion.OpenAITextGenerationService; import com.microsoft.semantickernel.exceptions.ConfigurationException; import com.microsoft.semantickernel.orchestration.FunctionResult; @@ -13,6 +14,7 @@ import com.microsoft.semantickernel.plugin.KernelPluginFactory; import com.microsoft.semantickernel.samples.plugins.ConversationSummaryPlugin; import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments.Builder; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; import reactor.core.publisher.Mono; @@ -21,7 +23,7 @@ * plugin. *

* Refer to the + * "https://github.com/microsoft/semantic-kernel-java/blob/main/samples/semantickernel-concepts/semantickernel-syntax-examples/README.md"> * README for configuring your environment to run the examples. */ public class Example13_ConversationSummaryPlugin { @@ -32,7 +34,7 @@ public class Example13_ConversationSummaryPlugin { // Only required if AZURE_CLIENT_KEY is set private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); private static final String MODEL_ID = System.getenv() - .getOrDefault("MODEL_ID", "text-davinci-003"); + .getOrDefault("MODEL_ID", "gpt-35-turbo"); private static final String chatTranscript = """ John: Hello, how are you? @@ -225,13 +227,13 @@ private static Kernel initializeKernel() { .buildAsyncClient(); } - TextGenerationService textGenerationService = OpenAITextGenerationService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); return Kernel.builder() - .withAIService(TextGenerationService.class, textGenerationService) + .withAIService(ChatCompletionService.class, openAIChatCompletion) .build(); } } diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example06_TemplateLanguage.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example06_TemplateLanguage.java similarity index 91% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example06_TemplateLanguage.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example06_TemplateLanguage.java index 86e61d51..698a2e1b 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example06_TemplateLanguage.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example06_TemplateLanguage.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.aiservices.openai.textcompletion.OpenAITextGenerationService; import com.microsoft.semantickernel.exceptions.ConfigurationException; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; @@ -15,6 +16,7 @@ import com.microsoft.semantickernel.semanticfunctions.KernelPromptTemplateFactory; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; public class Example06_TemplateLanguage { @@ -25,7 +27,7 @@ public class Example06_TemplateLanguage { // Only required if AZURE_CLIENT_KEY is set private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); private static final String MODEL_ID = System.getenv() - .getOrDefault("MODEL_ID", "text-davinci-003"); + .getOrDefault("MODEL_ID", "gpt-35-turbo"); public static void main(String[] args) throws ConfigurationException { @@ -44,13 +46,13 @@ public static void main(String[] args) throws ConfigurationException { .buildAsyncClient(); } - TextGenerationService textGenerationService = OpenAITextGenerationService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); Kernel kernel = Kernel.builder() - .withAIService(TextGenerationService.class, textGenerationService) + .withAIService(ChatCompletionService.class, openAIChatCompletion) .build(); // Load native plugin into the kernel function collection, sharing its functions with prompt templates diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example56_TemplateMethodFunctionsWithMultipleArguments.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example56_TemplateMethodFunctionsWithMultipleArguments.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example56_TemplateMethodFunctionsWithMultipleArguments.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example56_TemplateMethodFunctionsWithMultipleArguments.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example64_MultiplePromptTemplates.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example64_MultiplePromptTemplates.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example64_MultiplePromptTemplates.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/template/Example64_MultiplePromptTemplates.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/util/LocaleParser.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/util/LocaleParser.java similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/util/LocaleParser.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/util/LocaleParser.java diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionRoot/config.json b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionRoot/config.json similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionRoot/config.json rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionRoot/config.json diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionRoot/skprompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionRoot/skprompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionRoot/skprompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionRoot/skprompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionWithService/config.json b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionWithService/config.json similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionWithService/config.json rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionWithService/config.json diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionWithService/skprompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionWithService/skprompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionWithService/skprompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/ExamplePlugins/ExampleFunctionWithService/skprompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/Chat/config.json b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/Chat/config.json similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/Chat/config.json rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/Chat/config.json diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/Chat/skprompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/Chat/skprompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/Chat/skprompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/Chat/skprompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/getIntent.prompt.yaml b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/getIntent.prompt.yaml similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/getIntent.prompt.yaml rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/Prompts/getIntent.prompt.yaml diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/MakeAbstractReadable/config.json b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/MakeAbstractReadable/config.json similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/MakeAbstractReadable/config.json rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/MakeAbstractReadable/config.json diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/MakeAbstractReadable/skprompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/MakeAbstractReadable/skprompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/MakeAbstractReadable/skprompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/MakeAbstractReadable/skprompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Notegen/config.json b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Notegen/config.json similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Notegen/config.json rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Notegen/config.json diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Notegen/skprompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Notegen/skprompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Notegen/skprompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Notegen/skprompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Summarize/config.json b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Summarize/config.json similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Summarize/config.json rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Summarize/config.json diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Summarize/skprompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Summarize/skprompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Summarize/skprompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Summarize/skprompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Topics/config.json b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Topics/config.json similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Topics/config.json rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Topics/config.json diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Topics/skprompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Topics/skprompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Topics/skprompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/SummarizePlugin/Topics/skprompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/WriterPlugin/ShortPoem/config.json b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/WriterPlugin/ShortPoem/config.json similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/WriterPlugin/ShortPoem/config.json rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/WriterPlugin/ShortPoem/config.json diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/WriterPlugin/ShortPoem/skprompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/WriterPlugin/ShortPoem/skprompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/WriterPlugin/ShortPoem/skprompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/Plugins/WriterPlugin/ShortPoem/skprompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/Plugins/ExamplePlugins/ExampleFunction/config.json b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/Plugins/ExamplePlugins/ExampleFunction/config.json similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/Plugins/ExamplePlugins/ExampleFunction/config.json rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/Plugins/ExamplePlugins/ExampleFunction/config.json diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-system-prompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-system-prompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-system-prompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-system-prompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-user-context.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-user-context.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-user-context.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-user-context.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-user-prompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-user-prompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-user-prompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/30-user-prompt.txt diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/duke.png b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/duke.png new file mode 100644 index 00000000..31793b08 Binary files /dev/null and b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/chatcompletion/duke.png differ diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/java/GenerateStory.yaml b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/java/GenerateStory.yaml similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/java/GenerateStory.yaml rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/java/GenerateStory.yaml diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/java/GenerateStoryHandlebars.yaml b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/java/GenerateStoryHandlebars.yaml similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/java/GenerateStoryHandlebars.yaml rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/java/GenerateStoryHandlebars.yaml diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/Answer/AnswerQuestion/config.json b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/Answer/AnswerQuestion/config.json similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/Answer/AnswerQuestion/config.json rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/Answer/AnswerQuestion/config.json diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/Answer/AnswerQuestion/skprompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/Answer/AnswerQuestion/skprompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/Answer/AnswerQuestion/skprompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/Answer/AnswerQuestion/skprompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/ExamplePlugins/ExampleFunction/config.json b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/ExamplePlugins/ExampleFunction/config.json similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/ExamplePlugins/ExampleFunction/config.json rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/ExamplePlugins/ExampleFunction/config.json diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/require_context_variable_planner_prompt.txt b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/require_context_variable_planner_prompt.txt similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/require_context_variable_planner_prompt.txt rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/com/microsoft/semantickernel/samples/syntaxexamples/javaspecific/require_context_variable_planner_prompt.txt diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/log4j2.xml b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/log4j2.xml similarity index 100% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/log4j2.xml rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/resources/log4j2.xml diff --git a/java/samples/semantickernel-demos/booking-agent-m365/pom.xml b/samples/semantickernel-demos/booking-agent-m365/pom.xml similarity index 98% rename from java/samples/semantickernel-demos/booking-agent-m365/pom.xml rename to samples/semantickernel-demos/booking-agent-m365/pom.xml index 2df9a709..c427bb4e 100644 --- a/java/samples/semantickernel-demos/booking-agent-m365/pom.xml +++ b/samples/semantickernel-demos/booking-agent-m365/pom.xml @@ -4,7 +4,7 @@ com.microsoft.semantic-kernel semantickernel-demos - 1.1.5 + 1.2.0 ../pom.xml diff --git a/java/samples/semantickernel-demos/booking-agent-m365/src/main/java/com/microsoft/semantickernel/BookingAgent.java b/samples/semantickernel-demos/booking-agent-m365/src/main/java/com/microsoft/semantickernel/BookingAgent.java similarity index 100% rename from java/samples/semantickernel-demos/booking-agent-m365/src/main/java/com/microsoft/semantickernel/BookingAgent.java rename to samples/semantickernel-demos/booking-agent-m365/src/main/java/com/microsoft/semantickernel/BookingAgent.java diff --git a/java/samples/semantickernel-demos/booking-agent-m365/src/main/java/com/microsoft/semantickernel/BookingPlugin.java b/samples/semantickernel-demos/booking-agent-m365/src/main/java/com/microsoft/semantickernel/BookingPlugin.java similarity index 100% rename from java/samples/semantickernel-demos/booking-agent-m365/src/main/java/com/microsoft/semantickernel/BookingPlugin.java rename to samples/semantickernel-demos/booking-agent-m365/src/main/java/com/microsoft/semantickernel/BookingPlugin.java diff --git a/java/samples/semantickernel-demos/booking-agent-m365/src/main/resources/log4j2.xml b/samples/semantickernel-demos/booking-agent-m365/src/main/resources/log4j2.xml similarity index 100% rename from java/samples/semantickernel-demos/booking-agent-m365/src/main/resources/log4j2.xml rename to samples/semantickernel-demos/booking-agent-m365/src/main/resources/log4j2.xml diff --git a/java/samples/semantickernel-demos/pom.xml b/samples/semantickernel-demos/pom.xml similarity index 83% rename from java/samples/semantickernel-demos/pom.xml rename to samples/semantickernel-demos/pom.xml index 15f02813..4ef911aa 100644 --- a/java/samples/semantickernel-demos/pom.xml +++ b/samples/semantickernel-demos/pom.xml @@ -4,16 +4,17 @@ com.microsoft.semantic-kernel semantickernel-samples-parent - 1.1.5 + 1.2.0 ../pom.xml semantickernel-demos - 1.1.5 pom semantic-kernel-demos booking-agent-m365 + semantickernel-spring-starter + sk-presidio-sample diff --git a/java/samples/semantickernel-demos/semantickernel-spring-starter/pom.xml b/samples/semantickernel-demos/semantickernel-spring-starter/pom.xml similarity index 70% rename from java/samples/semantickernel-demos/semantickernel-spring-starter/pom.xml rename to samples/semantickernel-demos/semantickernel-spring-starter/pom.xml index e3f462b7..acd56827 100644 --- a/java/samples/semantickernel-demos/semantickernel-spring-starter/pom.xml +++ b/samples/semantickernel-demos/semantickernel-spring-starter/pom.xml @@ -1,19 +1,37 @@ - + 4.0.0 com.microsoft.semantic-kernel - samples - 0.2.14-alpha-SNAPSHOT + semantickernel-demos + 1.2.0 ../pom.xml semantickernel-spring-starter Semantic Kernel Spring Boot Starter + + + + com.microsoft.semantic-kernel + semantickernel-bom + ${project.version} + pom + import + + + + + com.microsoft.semantic-kernel + semantickernel-api + + + com.microsoft.semantic-kernel + semantickernel-aiservices-openai + + org.slf4j slf4j-api @@ -40,21 +58,6 @@ spring-boot 3.2.1 - - com.azure - azure-ai-openai - 1.0.0-beta.7 - - - com.microsoft.semantic-kernel - semantickernel-core - test - - - com.microsoft.semantic-kernel - semantickernel-connectors-ai-openai - test - org.springframework spring-test @@ -64,7 +67,7 @@ com.azure azure-identity - 1.9.1 + 1.12.2 org.junit.jupiter diff --git a/java/samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/AzureOpenAIConnectionProperties.java b/samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/AzureOpenAIConnectionProperties.java similarity index 96% rename from java/samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/AzureOpenAIConnectionProperties.java rename to samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/AzureOpenAIConnectionProperties.java index cf23fc8b..643006f8 100644 --- a/java/samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/AzureOpenAIConnectionProperties.java +++ b/samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/AzureOpenAIConnectionProperties.java @@ -1,3 +1,4 @@ +// Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.starter; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -29,14 +30,14 @@ public String getEndpoint() { return endpoint; } - public String getKey() { - return key; - } - public void setEndpoint(String endpoint) { this.endpoint = endpoint; } + public String getKey() { + return key; + } + public void setKey(String key) { this.key = key; } diff --git a/java/samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/SemanticKernelAutoConfiguration.java b/samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/SemanticKernelAutoConfiguration.java similarity index 86% rename from java/samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/SemanticKernelAutoConfiguration.java rename to samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/SemanticKernelAutoConfiguration.java index df35c130..b1d66cc0 100644 --- a/java/samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/SemanticKernelAutoConfiguration.java +++ b/samples/semantickernel-demos/semantickernel-spring-starter/src/main/java/com/microsoft/semantickernel/starter/SemanticKernelAutoConfiguration.java @@ -1,10 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.starter; import com.azure.ai.openai.OpenAIAsyncClient; import com.azure.ai.openai.OpenAIClientBuilder; import com.azure.core.credential.AzureKeyCredential; import com.microsoft.semantickernel.Kernel; -import com.microsoft.semantickernel.SKBuilders; +import com.microsoft.semantickernel.aiservices.openai.textcompletion.OpenAITextGenerationService; +import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -20,7 +22,21 @@ @EnableConfigurationProperties(AzureOpenAIConnectionProperties.class) public class SemanticKernelAutoConfiguration { - private static final Logger LOGGER = LoggerFactory.getLogger(SemanticKernelAutoConfiguration.class); + private static final Logger LOGGER = LoggerFactory.getLogger( + SemanticKernelAutoConfiguration.class); + + private static String setModelID(AzureOpenAIConnectionProperties connectionProperties) { + String modelId; + if (connectionProperties.getDeploymentName() == null) { + modelId = "text-davinci-003"; + LOGGER.warn( + "No deployment name specified, using default model id: " + modelId); + } else { + modelId = connectionProperties.getDeploymentName(); + LOGGER.info("Using model id: " + modelId); + } + return modelId; + } /** * Creates a {@link OpenAIAsyncClient} with the endpoint and key specified in the @@ -54,25 +70,12 @@ public OpenAIAsyncClient openAIAsyncClient( @Bean public Kernel semanticKernel(OpenAIAsyncClient client, AzureOpenAIConnectionProperties connectionProperties) { - return SKBuilders.kernel() - .withDefaultAIService( - SKBuilders.textCompletion() + return Kernel.builder() + .withAIService(TextGenerationService.class, + OpenAITextGenerationService.builder() .withModelId(setModelID(connectionProperties)) - .withOpenAIClient(client) + .withOpenAIAsyncClient(client) .build()) .build(); } - - private static String setModelID(AzureOpenAIConnectionProperties connectionProperties) { - String modelId; - if (connectionProperties.getDeploymentName() == null) { - modelId = "text-davinci-003"; - LOGGER.warn( - "No deployment name specified, using default model id: " + modelId); - } else { - modelId = connectionProperties.getDeploymentName(); - LOGGER.info("Using model id: " + modelId); - } - return modelId; - } } diff --git a/java/samples/semantickernel-demos/semantickernel-spring-starter/src/main/resources/application.properties b/samples/semantickernel-demos/semantickernel-spring-starter/src/main/resources/application.properties similarity index 100% rename from java/samples/semantickernel-demos/semantickernel-spring-starter/src/main/resources/application.properties rename to samples/semantickernel-demos/semantickernel-spring-starter/src/main/resources/application.properties diff --git a/samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/AzureOpenAIConnectionPropertiesTest.java b/samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/AzureOpenAIConnectionPropertiesTest.java new file mode 100644 index 00000000..56fd962a --- /dev/null +++ b/samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/AzureOpenAIConnectionPropertiesTest.java @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.azureopenai; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.microsoft.semantickernel.starter.AzureOpenAIConnectionProperties; +import com.microsoft.semantickernel.starter.SemanticKernelAutoConfiguration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.test.context.ActiveProfiles; + +@ActiveProfiles("test") +public class AzureOpenAIConnectionPropertiesTest { + + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues( + // @formatter:off + "client.azureopenai.key=TEST_KEY", + "client.azureopenai.endpoint=TEST_ENDPOINT", + "client.azureopenai.deploymentname=TEST_DEPLOYMENT_NAME" + + // @formatter:on + ) + .withConfiguration( + AutoConfigurations.of(SemanticKernelAutoConfiguration.class)); + + @Test + public void ConnectionPropertiesTest() { + contextRunner.run( + context -> { + AzureOpenAIConnectionProperties props = context + .getBean(AzureOpenAIConnectionProperties.class); + assertNotNull(props.getEndpoint()); + assertNotNull(props.getKey()); + assertNotNull(props.getDeploymentName()); + }); + } +} diff --git a/java/samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/SemanticKernelAutoConfigurationTest.java b/samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/SemanticKernelAutoConfigurationTest.java similarity index 95% rename from java/samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/SemanticKernelAutoConfigurationTest.java rename to samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/SemanticKernelAutoConfigurationTest.java index c4bec236..fa7419f4 100644 --- a/java/samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/SemanticKernelAutoConfigurationTest.java +++ b/samples/semantickernel-demos/semantickernel-spring-starter/src/test/java/com/microsoft/semantickernel/azureopenai/SemanticKernelAutoConfigurationTest.java @@ -1,3 +1,4 @@ +// Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.azureopenai; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -23,7 +24,7 @@ public class SemanticKernelAutoConfigurationTest { public void testSemanticKernelAutoConfig() { ApplicationContextRunner runner = new ApplicationContextRunner(); runner.withPropertyValues( - // @formatter:off + // @formatter:off "client.azureopenai.key=TEST_KEY", "client.azureopenai.endpoint=TEST_ENDPOINT" // @formatter:on diff --git a/java/samples/semantickernel-demos/semantickernel-spring-starter/src/test/resources/application.properties b/samples/semantickernel-demos/semantickernel-spring-starter/src/test/resources/application.properties similarity index 100% rename from java/samples/semantickernel-demos/semantickernel-spring-starter/src/test/resources/application.properties rename to samples/semantickernel-demos/semantickernel-spring-starter/src/test/resources/application.properties diff --git a/java/samples/semantickernel-demos/semantickernel-spring-starter/src/test/resources/log4j2.xml b/samples/semantickernel-demos/semantickernel-spring-starter/src/test/resources/log4j2.xml similarity index 100% rename from java/samples/semantickernel-demos/semantickernel-spring-starter/src/test/resources/log4j2.xml rename to samples/semantickernel-demos/semantickernel-spring-starter/src/test/resources/log4j2.xml diff --git a/samples/semantickernel-demos/sk-presidio-sample/.mvn/jvm.config b/samples/semantickernel-demos/sk-presidio-sample/.mvn/jvm.config new file mode 100644 index 00000000..32599cef --- /dev/null +++ b/samples/semantickernel-demos/sk-presidio-sample/.mvn/jvm.config @@ -0,0 +1,10 @@ +--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED diff --git a/samples/semantickernel-demos/sk-presidio-sample/.mvn/wrapper/maven-wrapper.properties b/samples/semantickernel-demos/sk-presidio-sample/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..6d3a5665 --- /dev/null +++ b/samples/semantickernel-demos/sk-presidio-sample/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/samples/semantickernel-demos/sk-presidio-sample/README.md b/samples/semantickernel-demos/sk-presidio-sample/README.md new file mode 100644 index 00000000..fe9f4c4e --- /dev/null +++ b/samples/semantickernel-demos/sk-presidio-sample/README.md @@ -0,0 +1,51 @@ +# Presidio Sample + +This sample demonstrates how to use Presidio with Semantic Kernel to redact sensitive data from a prompt. A sample +output from this example is shown below: + +``` +============================== +Input text is: +The users name is: Steven. +Steven has account number 012345612. +Steven was born in New York and their mother is Sally. +============================== +Anonymised text is: +The users name is: PERSON4. +PERSON4 has account number AU_ACN3. +PERSON4 was born in LOCATION1 and their mother is PERSON2. + +============================== +User Question: +Question: Where was the user born? +============================== +Anonymised response: +The user was born in LOCATION1. +============================== +Deanonymised response: +The user was born in New York. + +============================== +User Question: +Question: Who is the users mother? +============================== +Anonymised response: +The user's mother is PERSON2. +============================== +Deanonymised response: +The user's mother is Sally. +``` + +# App structure + +The [semantickernel-presidio-plugin](..%2F..%2Fsemantickernel-sample-plugins%2Fsemantickernel-presidio-plugin) plugin +takes user text and runs it through Presidio to redact sensitive data. The plugin then returns the redacted text. The +redacted information is then sent to an LLM for processing. The response is then de-anonymised. + +# Build and Run + +As this example depends on running Presidio, it run within docker containers using docker compose. + +- Before building and running ensure that you have run `./mvnw install` on the semantic kernel you wish to use. +- Copy [env.example](env.example) to `.env` and update the values as needed. +- Run [buildAndRun.sh](buildAndRun.sh) \ No newline at end of file diff --git a/samples/semantickernel-demos/sk-presidio-sample/buildAndRun.sh b/samples/semantickernel-demos/sk-presidio-sample/buildAndRun.sh new file mode 100755 index 00000000..43a1c466 --- /dev/null +++ b/samples/semantickernel-demos/sk-presidio-sample/buildAndRun.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -eux + +./mvnw package + +docker-compose build && docker-compose up \ No newline at end of file diff --git a/samples/semantickernel-demos/sk-presidio-sample/docker-compose.yml b/samples/semantickernel-demos/sk-presidio-sample/docker-compose.yml new file mode 100644 index 00000000..87435525 --- /dev/null +++ b/samples/semantickernel-demos/sk-presidio-sample/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3.6' +services: + presidio-analyzer: + image: "mcr.microsoft.com/presidio-analyzer" + ports: + - 3001:3000 + logging: + driver: none + presidio-anonymizer: + image: "mcr.microsoft.com/presidio-anonymizer" + ports: + - 3000:3000 + logging: + driver: none + sk-presidio-sample: + image: "sk-presidio-sample" + depends_on: + - presidio-anonymizer + - presidio-analyzer + build: + context: . + dockerfile: ./sk-presidio-sample-dockerfile + secrets: + - ai-config +secrets: + ai-config: + file: ./.env + diff --git a/samples/semantickernel-demos/sk-presidio-sample/env.example b/samples/semantickernel-demos/sk-presidio-sample/env.example new file mode 100644 index 00000000..85a2b743 --- /dev/null +++ b/samples/semantickernel-demos/sk-presidio-sample/env.example @@ -0,0 +1,7 @@ +# If OpenAI +#CLIENT_KEY="" + +# If Azure OpenAI +#USE_AZURE_CLIENT=true +#CLIENT_ENDPOINT="" +#AZURE_CLIENT_KEY="" diff --git a/samples/semantickernel-demos/sk-presidio-sample/mvnw b/samples/semantickernel-demos/sk-presidio-sample/mvnw new file mode 100755 index 00000000..8d937f4c --- /dev/null +++ b/samples/semantickernel-demos/sk-presidio-sample/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/samples/semantickernel-demos/sk-presidio-sample/pom.xml b/samples/semantickernel-demos/sk-presidio-sample/pom.xml new file mode 100644 index 00000000..11dd2ae6 --- /dev/null +++ b/samples/semantickernel-demos/sk-presidio-sample/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + com.microsoft.semantic-kernel + semantickernel-demos + 1.2.0 + ../pom.xml + + + sk-presidio-sample + Presidio Sample + + + + + com.microsoft.semantic-kernel + semantickernel-bom + ${project.version} + pom + import + + + + + + com.microsoft.semantic-kernel + semantickernel-api + + + com.microsoft.semantic-kernel + semantickernel-aiservices-openai + + + com.microsoft.semantic-kernel + semantickernel-presidio-plugin + ${project.version} + + + + org.slf4j + slf4j-api + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + maven-assembly-plugin + + + + com.microsoft.semantickernel.Main + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/samples/semantickernel-demos/sk-presidio-sample/scripts/run.sh b/samples/semantickernel-demos/sk-presidio-sample/scripts/run.sh new file mode 100644 index 00000000..7578ab41 --- /dev/null +++ b/samples/semantickernel-demos/sk-presidio-sample/scripts/run.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# It takes some time for presidio to start, wait for it +sleep 10 + +set -a +. /run/secrets/ai-config +set +a + +java -jar sk-presidio-sample.jar \ No newline at end of file diff --git a/samples/semantickernel-demos/sk-presidio-sample/sk-presidio-sample-dockerfile b/samples/semantickernel-demos/sk-presidio-sample/sk-presidio-sample-dockerfile new file mode 100644 index 00000000..7a5102a2 --- /dev/null +++ b/samples/semantickernel-demos/sk-presidio-sample/sk-presidio-sample-dockerfile @@ -0,0 +1,15 @@ +###################################################### +## Build Deployment +FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu as presidio-sk-sample-app + +COPY --chown=app:app scripts/run.sh /home/app/ + +RUN chmod +x /home/app/run.sh + +WORKDIR /home/app +USER app + +COPY target/sk-presidio-sample-*-jar-with-dependencies.jar /home/app/sk-presidio-sample.jar + +CMD /home/app/run.sh + diff --git a/samples/semantickernel-demos/sk-presidio-sample/src/main/java/com/microsoft/semantickernel/Main.java b/samples/semantickernel-demos/sk-presidio-sample/src/main/java/com/microsoft/semantickernel/Main.java new file mode 100644 index 00000000..366d5014 --- /dev/null +++ b/samples/semantickernel-demos/sk-presidio-sample/src/main/java/com/microsoft/semantickernel/Main.java @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.credential.KeyCredential; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; +import com.microsoft.semantickernel.contextvariables.ContextVariableTypes; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.plugin.KernelPluginFactory; +import com.microsoft.semantickernel.presidio.AnonymizedText; +import com.microsoft.semantickernel.presidio.AnonymizedTextConverter; +import com.microsoft.semantickernel.presidio.RedactorPlugin; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; +import com.microsoft.semantickernel.services.ServiceNotFoundException; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import java.util.List; +import reactor.core.publisher.Mono; + +public class Main { + + private static final String USE_AZURE_CLIENT = System.getenv("USE_AZURE_CLIENT"); + + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); + + // Only required if AZURE_CLIENT_KEY is set + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + private static final String MODEL_ID = System.getenv() + .getOrDefault("MODEL_ID", "gpt-4o"); + + public static void main(String[] args) throws InterruptedException { + + Kernel kernel = buildKernel(); + + String text = """ + The users name is: Steven. + Steven has account number 012345612. + Steven was born in New York and their mother is Sally. + """.stripIndent(); + + System.out.println("=============================="); + System.out.println("Input text is: \n" + text); + + AnonymizedText result = kernel + .invokeAsync("redactor", "redact") + .withResultType(AnonymizedText.class) + .withArguments( + KernelFunctionArguments.builder() + .withVariable("input", text) + .build()) + .block() + .getResult(); + + System.out.println("=============================="); + System.out.println("Anonymised text is: \n" + result.getRedacted()); + + askQuestion(kernel, result, "Question: Where was the user born?").block(); + askQuestion(kernel, result, "Question: Who is the users mother?").block(); + + } + + private static Mono>> askQuestion( + Kernel kernel, + AnonymizedText anonymizedUserInfo, + String question) { + ChatHistory chat = formChatHistory(); + + chat.addUserMessage(anonymizedUserInfo.getRedacted()); + chat.addUserMessage(question); + + System.out.println("=============================="); + System.out.println("User Question: \n" + question); + + try { + return kernel + .getService(ChatCompletionService.class) + .getChatMessageContentsAsync(chat, kernel, + InvocationContext.builder() + .withPromptExecutionSettings( + PromptExecutionSettings.builder() + .withMaxTokens(2048) + .withTemperature(0.5) + .build()) + .build()) + .map(chatHistory -> { + String message = chatHistory.get(0).getContent(); + System.out.println("=============================="); + System.out.println( + "Anonymised response: \n" + message); + + System.out.println("=============================="); + System.out.println( + "Deanonymised response: \n" + anonymizedUserInfo.unredact(message)); + return chatHistory; + }); + + } catch (ServiceNotFoundException e) { + throw new RuntimeException(e); + } + } + + private static ChatHistory formChatHistory() { + ChatHistory chat = new ChatHistory(); + + chat.addSystemMessage( + """ + You answer questions about the provided information. + The following is an example of answering a question about a user. + + Information about the user: + The users name is PERSON100. + PERSON100 has long hair. + + Question: What does the users hair look like? + Answer: The user has long hair. + """.stripIndent()); + + return chat; + } + + private static Kernel buildKernel() { + OpenAIAsyncClient client; + + if (Boolean.parseBoolean(USE_AZURE_CLIENT)) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(AZURE_CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + ChatCompletionService chat = OpenAIChatCompletion.builder() + .withModelId(MODEL_ID) + .withOpenAIAsyncClient(client) + .build(); + + ContextVariableTypes.addGlobalConverter(new AnonymizedTextConverter()); + + return Kernel + .builder() + .withAIService(ChatCompletionService.class, chat) + .withPlugin( + KernelPluginFactory.createFromObject( + new RedactorPlugin( + "http://presidio-analyzer:3000", + "http://presidio-anonymizer:3000"), + "redactor")) + .build(); + + } + +} diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/log4j2.xml b/samples/semantickernel-demos/sk-presidio-sample/src/main/resources/log4j2.xml similarity index 100% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/log4j2.xml rename to samples/semantickernel-demos/sk-presidio-sample/src/main/resources/log4j2.xml diff --git a/java/samples/semantickernel-learn-resources/pom.xml b/samples/semantickernel-learn-resources/pom.xml similarity index 98% rename from java/samples/semantickernel-learn-resources/pom.xml rename to samples/semantickernel-learn-resources/pom.xml index 54be08fc..63ee1288 100644 --- a/java/samples/semantickernel-learn-resources/pom.xml +++ b/samples/semantickernel-learn-resources/pom.xml @@ -4,7 +4,7 @@ com.microsoft.semantic-kernel semantickernel-samples-parent - 1.1.5 + 1.2.0 ../pom.xml diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/AIServices.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/AIServices.java similarity index 93% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/AIServices.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/AIServices.java index d7a5a82d..09df9f87 100644 --- a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/AIServices.java +++ b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/AIServices.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; @@ -44,7 +45,7 @@ public static void main(String[] args) { } // - ChatCompletionService chatCompletionService = ChatCompletionService.builder() + ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder() .withModelId(CHAT_MODEL_ID) .withOpenAIAsyncClient(client) .build(); diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/ConfiguringPrompts.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/ConfiguringPrompts.java similarity index 95% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/ConfiguringPrompts.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/ConfiguringPrompts.java index 2f73c58b..df05d8a4 100644 --- a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/ConfiguringPrompts.java +++ b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/ConfiguringPrompts.java @@ -8,6 +8,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import com.microsoft.semantickernel.semanticfunctions.InputVariable; import com.microsoft.semantickernel.semanticfunctions.KernelFunction; @@ -47,7 +48,7 @@ public static void main(String[] args) { } // - ChatCompletionService chatCompletionService = ChatCompletionService.builder() + ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder() .withModelId(CHAT_MODEL_ID) .withOpenAIAsyncClient(client) .build(); diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/CreatingFunctions.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/CreatingFunctions.java similarity index 97% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/CreatingFunctions.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/CreatingFunctions.java index d009ecbe..2cbb5baa 100644 --- a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/CreatingFunctions.java +++ b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/CreatingFunctions.java @@ -44,7 +44,7 @@ public static void main(String[] args) { // Kernel kernel = Kernel.builder() - .withAIService(ChatCompletionService.class, ChatCompletionService.builder() + .withAIService(ChatCompletionService.class, OpenAIChatCompletion.builder() .withModelId(MODEL_ID) .withOpenAIAsyncClient(client) .build()) diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/FunctionsWithinPrompts.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/FunctionsWithinPrompts.java similarity index 97% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/FunctionsWithinPrompts.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/FunctionsWithinPrompts.java index fcb0c1dc..f271b757 100644 --- a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/FunctionsWithinPrompts.java +++ b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/FunctionsWithinPrompts.java @@ -9,6 +9,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.ToolCallBehavior; import com.microsoft.semantickernel.plugin.KernelPlugin; @@ -54,7 +55,7 @@ public static void main(String[] args) { } // - ChatCompletionService chatCompletionService = ChatCompletionService.builder() + ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder() .withModelId(MODEL_ID) .withOpenAIAsyncClient(client) .build(); diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Plugin.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Plugin.java similarity index 96% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Plugin.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Plugin.java index eff1a254..c0ed21eb 100644 --- a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Plugin.java +++ b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Plugin.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.implementation.CollectionUtil; import com.microsoft.semantickernel.orchestration.InvocationContext; import com.microsoft.semantickernel.orchestration.ToolCallBehavior; @@ -55,7 +56,7 @@ public static void main(String[] args) { } // - ChatCompletionService chatCompletionService = ChatCompletionService.builder() + ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder() .withModelId(MODEL_ID) .withOpenAIAsyncClient(client) .build(); diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Prompts.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Prompts.java similarity index 98% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Prompts.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Prompts.java index 82ce0e3c..20051eb8 100644 --- a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Prompts.java +++ b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Prompts.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; public class Prompts { @@ -35,7 +36,7 @@ public static void main(String[] args) { // Kernel kernel = Kernel.builder() - .withAIService(ChatCompletionService.class, ChatCompletionService.builder() + .withAIService(ChatCompletionService.class, OpenAIChatCompletion.builder() .withModelId(MODEL_ID) .withOpenAIAsyncClient(client) .build()) diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/SerializingPrompts.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/SerializingPrompts.java similarity index 95% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/SerializingPrompts.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/SerializingPrompts.java index 5cd4bd20..3b5ba591 100644 --- a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/SerializingPrompts.java +++ b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/SerializingPrompts.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.contextvariables.converters.CollectionVariableContextVariableTypeConverter; import com.microsoft.semantickernel.plugin.KernelPluginFactory; import com.microsoft.semantickernel.samples.plugins.ConversationSummaryPlugin; @@ -39,7 +40,7 @@ public class SerializingPrompts { private static final String MODEL_ID = System.getenv() .getOrDefault("MODEL_ID", "gpt-3.5-turbo"); - private static final String PLUGINS_DIR = "java/samples/semantickernel-learn-resources/src/main/resources/Plugins"; + private static final String PLUGINS_DIR = "samples/semantickernel-learn-resources/src/main/resources/Plugins"; public static void main(String[] args) throws IOException { System.out.println("======== Serializing Prompts ========"); @@ -79,7 +80,7 @@ public static void main(String[] args) throws IOException { // // Create Kernel Kernel kernel = Kernel.builder() - .withAIService(ChatCompletionService.class, ChatCompletionService.builder() + .withAIService(ChatCompletionService.class, OpenAIChatCompletion.builder() .withModelId(MODEL_ID) .withOpenAIAsyncClient(client) .build()) diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Templates.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Templates.java similarity index 97% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Templates.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Templates.java index d030b4e5..41b09651 100644 --- a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Templates.java +++ b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/Templates.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter; import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.ToolCallBehavior; @@ -72,7 +73,7 @@ public static void main(String[] args) { }) .build(); - ChatCompletionService chatCompletionService = ChatCompletionService.builder() + ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder() .withModelId(MODEL_ID) .withOpenAIAsyncClient(client) .build(); diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/UsingTheKernel.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/UsingTheKernel.java similarity index 95% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/UsingTheKernel.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/UsingTheKernel.java index e19a2ad1..73f137dc 100644 --- a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/UsingTheKernel.java +++ b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/documentationexamples/UsingTheKernel.java @@ -6,6 +6,7 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.plugin.KernelPlugin; import com.microsoft.semantickernel.plugin.KernelPluginFactory; import com.microsoft.semantickernel.samples.plugins.MathPlugin; @@ -48,7 +49,7 @@ public static void main(String[] args) { // - ChatCompletionService chatCompletionService = ChatCompletionService.builder() + ChatCompletionService chatCompletionService = OpenAIChatCompletion.builder() .withModelId(MODEL_ID) .withOpenAIAsyncClient(client) .build(); diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/ConversationSummaryPlugin.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/ConversationSummaryPlugin.java similarity index 100% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/ConversationSummaryPlugin.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/ConversationSummaryPlugin.java diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/MathPlugin.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/MathPlugin.java similarity index 100% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/MathPlugin.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/MathPlugin.java diff --git a/java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/PromptFunctionConstants.java b/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/PromptFunctionConstants.java similarity index 100% rename from java/samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/PromptFunctionConstants.java rename to samples/semantickernel-learn-resources/src/main/java/com/microsoft/semantickernel/samples/plugins/PromptFunctionConstants.java diff --git a/java/samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/Chat/config.json b/samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/Chat/config.json similarity index 100% rename from java/samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/Chat/config.json rename to samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/Chat/config.json diff --git a/java/samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/Chat/skprompt.txt b/samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/Chat/skprompt.txt similarity index 100% rename from java/samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/Chat/skprompt.txt rename to samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/Chat/skprompt.txt diff --git a/java/samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/getIntent.prompt.yaml b/samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/getIntent.prompt.yaml similarity index 100% rename from java/samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/getIntent.prompt.yaml rename to samples/semantickernel-learn-resources/src/main/resources/Plugins/Prompts/getIntent.prompt.yaml diff --git a/java/samples/semantickernel-learn-resources/src/main/resources/log4j2.xml b/samples/semantickernel-learn-resources/src/main/resources/log4j2.xml similarity index 100% rename from java/samples/semantickernel-learn-resources/src/main/resources/log4j2.xml rename to samples/semantickernel-learn-resources/src/main/resources/log4j2.xml diff --git a/java/samples/semantickernel-sample-plugins/pom.xml b/samples/semantickernel-sample-plugins/pom.xml similarity index 88% rename from java/samples/semantickernel-sample-plugins/pom.xml rename to samples/semantickernel-sample-plugins/pom.xml index 79365be6..31bd11cc 100644 --- a/java/samples/semantickernel-sample-plugins/pom.xml +++ b/samples/semantickernel-sample-plugins/pom.xml @@ -4,16 +4,16 @@ com.microsoft.semantic-kernel semantickernel-samples-parent - 1.1.5 + 1.2.0 ../pom.xml semantickernel-sample-plugins - 1.1.5 pom semantickernel-sample-plugins semantickernel-openapi-plugin + semantickernel-presidio-plugin diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/pom.xml b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/pom.xml similarity index 95% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/pom.xml rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/pom.xml index 679c9762..6ac975d2 100644 --- a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/pom.xml +++ b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/pom.xml @@ -4,7 +4,7 @@ com.microsoft.semantic-kernel semantickernel-sample-plugins - 1.1.5 + 1.2.0 ../pom.xml @@ -83,6 +83,11 @@ log4j-slf4j2-impl test + + org.junit.jupiter + junit-jupiter-api + test + diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/OpenAPIHttpRequestPlugin.java b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/OpenAPIHttpRequestPlugin.java similarity index 100% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/OpenAPIHttpRequestPlugin.java rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/OpenAPIHttpRequestPlugin.java diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/SemanticKernelOpenAPIImporter.java b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/SemanticKernelOpenAPIImporter.java similarity index 100% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/SemanticKernelOpenAPIImporter.java rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/SemanticKernelOpenAPIImporter.java diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleAdoptiumImporter.java b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleAdoptiumImporter.java similarity index 100% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleAdoptiumImporter.java rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleAdoptiumImporter.java diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleAuthenticatedRequestImporter.java b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleAuthenticatedRequestImporter.java similarity index 100% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleAuthenticatedRequestImporter.java rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleAuthenticatedRequestImporter.java diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleOpenAPIParent.java b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleOpenAPIParent.java similarity index 90% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleOpenAPIParent.java rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleOpenAPIParent.java index 9cfc77a3..5132af8f 100644 --- a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleOpenAPIParent.java +++ b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExampleOpenAPIParent.java @@ -5,6 +5,7 @@ import com.azure.core.credential.KeyCredential; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.Kernel.Builder; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; public class ExampleOpenAPIParent { @@ -35,7 +36,7 @@ public static Builder kernelBuilder() { .buildAsyncClient(); } - ChatCompletionService openAIChatCompletion = ChatCompletionService.builder() + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() .withOpenAIAsyncClient(client) .withModelId(MODEL_ID) .build(); diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExamplePetstoreImporter.java b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExamplePetstoreImporter.java similarity index 100% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExamplePetstoreImporter.java rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/java/ExamplePetstoreImporter.java diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/adoptium.json b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/adoptium.json similarity index 100% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/adoptium.json rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/adoptium.json diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/adoptium.yaml b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/adoptium.yaml similarity index 100% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/adoptium.yaml rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/adoptium.yaml diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/authenticatedRequest.yaml b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/authenticatedRequest.yaml similarity index 100% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/authenticatedRequest.yaml rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/authenticatedRequest.yaml diff --git a/java/semantickernel-api/src/test/resources/log4j2.xml b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/log4j2.xml similarity index 100% rename from java/semantickernel-api/src/test/resources/log4j2.xml rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/log4j2.xml diff --git a/java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/petstore.yaml b/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/petstore.yaml similarity index 100% rename from java/samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/petstore.yaml rename to samples/semantickernel-sample-plugins/semantickernel-openapi-plugin/src/test/resources/petstore.yaml diff --git a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/pom.xml b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/pom.xml similarity index 90% rename from java/samples/semantickernel-concepts/semantickernel-syntax-examples/pom.xml rename to samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/pom.xml index 0cc358b1..cd3c0431 100644 --- a/java/samples/semantickernel-concepts/semantickernel-syntax-examples/pom.xml +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/pom.xml @@ -3,14 +3,13 @@ 4.0.0 com.microsoft.semantic-kernel - semantickernel-concepts - 1.1.5 + semantickernel-sample-plugins + 1.2.0 ../pom.xml - semantickernel-syntax-examples - Semantic Kernel Samples - Sample code for using the Semantic Kernel project + semantickernel-presidio-plugin + semantickernel-presidio-plugin jar @@ -61,8 +60,9 @@ azure-identity - com.microsoft.semantic-kernel - semantickernel-aiservices-openai + org.junit.jupiter + junit-jupiter-api + test diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/AnonymizedText.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/AnonymizedText.java new file mode 100644 index 00000000..6de328b6 --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/AnonymizedText.java @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio; + +import java.util.Collections; +import java.util.Map; + +public class AnonymizedText { + + private final String unRedacted; + private final String redacted; + private final Map redactedTokenMap; + + public AnonymizedText( + String unRedacted, + String redacted, + Map redactedTokenMap) { + this.unRedacted = unRedacted; + this.redacted = redacted; + this.redactedTokenMap = Collections.unmodifiableMap(redactedTokenMap); + } + + public String getUnRedacted() { + return unRedacted; + } + + public String getRedacted() { + return redacted; + } + + public Map getRedactedTokenMap() { + return redactedTokenMap; + } + + public String unredact(String message) { + return redactedTokenMap + .entrySet() + .stream() + .reduce(message, + (msg, entry) -> msg.replaceAll(entry.getKey(), entry.getValue()), + (msg1, msg2) -> msg1 + msg2); + } +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/AnonymizedTextConverter.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/AnonymizedTextConverter.java new file mode 100644 index 00000000..ab00fed9 --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/AnonymizedTextConverter.java @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio; + +import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter; + +public class AnonymizedTextConverter extends ContextVariableTypeConverter { + + public AnonymizedTextConverter() { + super( + AnonymizedText.class, + it -> (AnonymizedText) it, + AnonymizedText::getRedacted, + text -> { + throw new UnsupportedOperationException("AnonymizedText is write-only"); + }); + } +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/PresidioAnalysisClient.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/PresidioAnalysisClient.java new file mode 100644 index 00000000..d9e67805 --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/PresidioAnalysisClient.java @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio; + +import com.azure.core.http.ContentType; +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.microsoft.semantickernel.presidio.models.AnalyzerRequest; +import com.microsoft.semantickernel.presidio.models.AnalyzerResult; +import java.net.URL; +import java.util.List; +import reactor.core.publisher.Mono; + +public class PresidioAnalysisClient extends PresidioApiClient { + + public PresidioAnalysisClient( + HttpClient client, + URL endpoint) { + super(client, endpoint); + } + + public Mono> analyze(AnalyzerRequest analyzerRequest) { + try { + HttpRequest request = new HttpRequest(HttpMethod.POST, + new URL(super.endpoint, "/analyze")) + .setHeader(HttpHeaderName.CONTENT_TYPE, ContentType.APPLICATION_JSON) + .setBody(super.mapper.writeValueAsBytes(analyzerRequest)); + + return super.client.send(request) + .flatMap(httpResponse -> { + if (httpResponse.getStatusCode() != 200) { + return Mono.error( + new RuntimeException( + "Request failed: " + httpResponse.getStatusCode())); + } else { + return httpResponse.getBodyAsString(); + } + }) + .flatMap(body -> { + try { + TypeReference> type = new TypeReference<>() { + }; + + return Mono.just(mapper.readValue(body, type)); + } catch (JsonProcessingException e) { + return Mono.error( + new RuntimeException("Failed to parse response", e)); + } + }); + } catch (Exception e) { + return Mono.error(e); + } + } + + public Mono> analyze(String text, String language) { + return analyze(new AnalyzerRequest(text, language)); + } + +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/PresidioAnonmizerClient.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/PresidioAnonmizerClient.java new file mode 100644 index 00000000..fa4979ad --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/PresidioAnonmizerClient.java @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio; + +import com.azure.core.http.ContentType; +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.microsoft.semantickernel.presidio.models.AnalyzerResult; +import com.microsoft.semantickernel.presidio.models.AnonymizeRequest; +import com.microsoft.semantickernel.presidio.models.AnonymizerResult; +import com.microsoft.semantickernel.presidio.models.anonymizerType.AnonymizerType; +import java.net.URL; +import java.util.List; +import java.util.Map; +import reactor.core.publisher.Mono; + +public class PresidioAnonmizerClient extends PresidioApiClient { + + public PresidioAnonmizerClient( + HttpClient client, + URL endpoint) { + super(client, endpoint); + } + + public Mono anonymize(String text, + Map anonymizers, + List analyzerResults) { + + return anonymize(new AnonymizeRequest(text, anonymizers, analyzerResults)); + } + + private Mono anonymize(AnonymizeRequest anonymizeRequest) { + + try { + HttpRequest request = new HttpRequest(HttpMethod.POST, + new URL(super.endpoint, "/anonymize")) + .setHeader(HttpHeaderName.CONTENT_TYPE, ContentType.APPLICATION_JSON) + .setBody(super.mapper.writeValueAsBytes(anonymizeRequest)); + + return super.client.send(request) + .flatMap(httpResponse -> { + if (httpResponse.getStatusCode() >= 400) { + return Mono.error( + new RuntimeException( + "Request failed: " + httpResponse.getStatusCode())); + } else { + return httpResponse.getBodyAsString(); + } + }) + .flatMap(body -> { + try { + TypeReference type = new TypeReference<>() { + }; + + return Mono.just(mapper.readValue(body, type)); + } catch (JsonProcessingException e) { + return Mono.error( + new RuntimeException("Failed to parse response", e)); + } + }); + } catch (Exception e) { + return Mono.error(e); + } + + } +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/PresidioApiClient.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/PresidioApiClient.java new file mode 100644 index 00000000..8f2ea06c --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/PresidioApiClient.java @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio; + +import com.azure.core.http.HttpClient; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.net.URL; + +public abstract class PresidioApiClient { + + protected final HttpClient client; + protected final URL endpoint; + protected final ObjectMapper mapper; + + public PresidioApiClient( + HttpClient client, + URL endpoint) { + this.client = client; + this.endpoint = endpoint; + this.mapper = new ObjectMapper(); + } +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/RedactorPlugin.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/RedactorPlugin.java new file mode 100644 index 00000000..a8e156b4 --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/RedactorPlugin.java @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio; + +import com.azure.core.http.HttpClient; +import com.microsoft.semantickernel.presidio.models.AnalyzerResult; +import com.microsoft.semantickernel.presidio.models.anonymizerType.AnonymizerType.Replace; +import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; +import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; +import com.microsoft.semantickernel.semanticfunctions.annotations.SKSample; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; + +public class RedactorPlugin { + + private static final Logger LOGGER = LoggerFactory.getLogger(RedactorPlugin.class); + + private final PresidioAnalysisClient analysisClient; + + private final PresidioAnonmizerClient anonymizeClient; + + public RedactorPlugin( + String analysisServer, + String anonymizeServer) { + URL analysisUrl = null; + try { + analysisUrl = URI.create(analysisServer).toURL(); + } catch (MalformedURLException e) { + LOGGER.error("Failed to parse url", e); + } + + analysisClient = new PresidioAnalysisClient( + HttpClient.createDefault(), + analysisUrl); + + URL anonymizeUrl = null; + try { + anonymizeUrl = URI.create(anonymizeServer).toURL(); + } catch (MalformedURLException e) { + LOGGER.error("Failed to parse url", e); + } + + anonymizeClient = new PresidioAnonmizerClient( + HttpClient.createDefault(), + anonymizeUrl); + } + + @DefineKernelFunction(name = "redact", description = "Takes a string of data and redacts sensitive data from that text.", returnType = "com.microsoft.semantickernel.presidio.AnonymizedText", returnDescription = "The redacted text", samples = { + @SKSample(inputs = "Bob is tall.", output = "PERSON1 is tall.") + }) + public Mono redactData( + @KernelFunctionParameter(name = "input", description = "Text to be redacted") String text) { + return analysisClient + .analyze(text, "en") + .flatMap(analysisResult -> { + Map> grouped = groupResultsByAnonymizedData( + analysisResult, text); + + Map requestMaps = formAnonymizersMap(grouped); + + List allAnalyses = collectAllAnalyses(grouped); + + return anonymizeClient + .anonymize(text, requestMaps, allAnalyses) + .map(redacted -> { + Map anonymizedTokenMap = formAnonymizedTokenMap(grouped); + + return new AnonymizedText( + text, + redacted.text(), + anonymizedTokenMap); + }); + }); + + } + + private static Map formAnonymizedTokenMap( + Map> grouped) { + return grouped + .entrySet() + .stream() + .collect(Collectors.toMap( + entry -> entry.getValue().get(0).entityType(), + Entry::getKey)); + } + + private static List collectAllAnalyses( + Map> grouped) { + return grouped.values() + .stream() + .flatMap(Collection::stream) + .toList(); + } + + private static Map formAnonymizersMap( + Map> grouped) { + return grouped + .entrySet() + .stream() + .collect(Collectors.toMap( + entry -> entry.getValue().get(0).entityType(), + entry -> new Replace(entry.getValue().get(0).entityType()))); + } + + private static Map> groupResultsByAnonymizedData( + List analysisResult, String text) { + Map> grouped = analysisResult + .stream() + .collect(Collectors.groupingBy( + analyzerResult -> text.substring(analyzerResult.start(), + analyzerResult.end()))); + + AtomicInteger count = new AtomicInteger(0); + grouped = grouped + .entrySet() + .stream() + .collect(Collectors.toMap(Entry::getKey, + entry -> { + count.incrementAndGet(); + return entry.getValue().stream().map( + r -> new AnalyzerResult( + r.start(), + r.end(), + r.score(), + r.entityType() + count.get(), + r.recognitionMetadata(), + r.analysisExplanation())) + .toList(); + })); + return grouped; + } +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnalysisExplanation.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnalysisExplanation.java new file mode 100644 index 00000000..d135503b --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnalysisExplanation.java @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio.models; + +public record AnalysisExplanation( + String recognizer, + String pattern_name, + String pattern, + Float original_score, + Float score, + String textual_explanation, + Float score_context_improvement, + String supportive_context_word, + Float validation_result +) { + +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnalyzerRequest.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnalyzerRequest.java new file mode 100644 index 00000000..e2e28d97 --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnalyzerRequest.java @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio.models; + +public record AnalyzerRequest(String text, String language) { + +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnalyzerResult.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnalyzerResult.java new file mode 100644 index 00000000..65adb9cc --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnalyzerResult.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +public record AnalyzerResult( + Integer start, + Integer end, + Float score, + @JsonProperty("entity_type") + String entityType, + @JsonProperty("recognition_metadata") + RecognitionMetadata recognitionMetadata, + @JsonProperty("analysis_explanation") + AnalysisExplanation analysisExplanation +) { + +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnonymizeRequest.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnonymizeRequest.java new file mode 100644 index 00000000..a3de4d34 --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnonymizeRequest.java @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.microsoft.semantickernel.presidio.models.anonymizerType.AnonymizerType; +import java.util.List; +import java.util.Map; + +public record AnonymizeRequest( + String text, + Map anonymizers, + @JsonProperty("analyzer_results") + List analyzerResults) { + +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/Anonymizer.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/Anonymizer.java new file mode 100644 index 00000000..891b2fda --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/Anonymizer.java @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio.models; + +public record Anonymizer( + Integer charsToMask, + boolean fromEnd, + String maskingChar, + String type +) { + +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnonymizerItem.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnonymizerItem.java new file mode 100644 index 00000000..edac2185 --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnonymizerItem.java @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio.models; + +public record AnonymizerItem( + String operator, + String entity_type, + String text, + Integer start, + Integer end +) { + +} \ No newline at end of file diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnonymizerResult.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnonymizerResult.java new file mode 100644 index 00000000..d601daa2 --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/AnonymizerResult.java @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio.models; + +import java.util.List; + +public record AnonymizerResult( + String text, + List items) { + +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/RecognitionMetadata.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/RecognitionMetadata.java new file mode 100644 index 00000000..9d23d963 --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/RecognitionMetadata.java @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record RecognitionMetadata( + @JsonProperty("recognizer_identifier") + String recognizerIdentifier, + @JsonProperty("recognizer_name") + String recognizerName +) { + +} diff --git a/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/anonymizerType/AnonymizerType.java b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/anonymizerType/AnonymizerType.java new file mode 100644 index 00000000..8bb22ee2 --- /dev/null +++ b/samples/semantickernel-sample-plugins/semantickernel-presidio-plugin/src/main/java/com/microsoft/semantickernel/presidio/models/anonymizerType/AnonymizerType.java @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.presidio.models.anonymizerType; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class AnonymizerType { + + private String type; + + AnonymizerType( + @JsonProperty("type") String type) { + this.type = type; + } + + @JsonProperty("type") + public String getType() { + return type; + } + + public static class Replace extends AnonymizerType { + + private final String new_value; + + @JsonCreator + public Replace( + @JsonProperty("new_value") String new_value) { + super("replace"); + this.new_value = new_value; + } + + @JsonProperty("new_value") + public String getNewValue() { + return new_value; + } + } +} diff --git a/java/semantickernel-api/pom.xml b/semantickernel-api/pom.xml similarity index 88% rename from java/semantickernel-api/pom.xml rename to semantickernel-api/pom.xml index a47c141c..2bb0a050 100644 --- a/java/semantickernel-api/pom.xml +++ b/semantickernel-api/pom.xml @@ -6,7 +6,7 @@ com.microsoft.semantic-kernel semantickernel-parent - 1.1.5 + 1.2.0 ../pom.xml @@ -70,13 +70,24 @@ 3.3.1 test + + + javax.xml.stream + stax-api + provided + org.mockito mockito-junit-jupiter 5.11.0 + test + + io.opentelemetry + opentelemetry-api + diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Kernel.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/Kernel.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Kernel.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/Kernel.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/KernelPluginCollection.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/KernelPluginCollection.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/KernelPluginCollection.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/KernelPluginCollection.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/SemanticKernelBuilder.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/SemanticKernelBuilder.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/SemanticKernelBuilder.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/SemanticKernelBuilder.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/connectors/WebSearchEngineConnector.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/connectors/WebSearchEngineConnector.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/connectors/WebSearchEngineConnector.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/connectors/WebSearchEngineConnector.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/CaseInsensitiveMap.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/CaseInsensitiveMap.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/CaseInsensitiveMap.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/CaseInsensitiveMap.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariable.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariable.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariable.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariable.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableType.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableType.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableType.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableType.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableTypes.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableTypes.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableTypes.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/ContextVariableTypes.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/BooleanVariableContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/BooleanVariableContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/BooleanVariableContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/BooleanVariableContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CharacterVariableContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CharacterVariableContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CharacterVariableContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CharacterVariableContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/ChatHistoryVariableContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/ChatHistoryVariableContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/ChatHistoryVariableContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/ChatHistoryVariableContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CollectionVariableContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CollectionVariableContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CollectionVariableContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CollectionVariableContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CompletionUsageContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CompletionUsageContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CompletionUsageContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/CompletionUsageContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/DateTimeContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/DateTimeContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/DateTimeContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/DateTimeContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/InstantContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/InstantContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/InstantContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/InstantContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/NumberVariableContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/NumberVariableContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/NumberVariableContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/NumberVariableContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/PrimitiveBooleanVariableContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/PrimitiveBooleanVariableContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/PrimitiveBooleanVariableContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/PrimitiveBooleanVariableContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/PrimitiveVariableContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/PrimitiveVariableContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/PrimitiveVariableContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/PrimitiveVariableContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/StringVariableContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/StringVariableContextVariableTypeConverter.java similarity index 58% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/StringVariableContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/StringVariableContextVariableTypeConverter.java index 32fe4200..4a431e60 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/StringVariableContextVariableTypeConverter.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/StringVariableContextVariableTypeConverter.java @@ -3,8 +3,10 @@ import static com.microsoft.semantickernel.contextvariables.ContextVariableTypes.convert; +import com.microsoft.semantickernel.contextvariables.ContextVariable; import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter; import com.microsoft.semantickernel.contextvariables.ContextVariableTypes; +import javax.annotation.Nullable; /** * A {@link ContextVariableTypeConverter} for {@code java.lang.String} variables. Use @@ -22,8 +24,29 @@ public class StringVariableContextVariableTypeConverter extends public StringVariableContextVariableTypeConverter() { super( String.class, - s -> convert(s, String.class), + StringVariableContextVariableTypeConverter::convertToString, ContextVariableTypeConverter::escapeXmlString, s -> s); } + + @Nullable + public static String convertToString(@Nullable Object s) { + String converted = convert(s, String.class); + if (converted != null) { + return converted; + } + + if (s instanceof ContextVariable) { + s = ((ContextVariable) s).getValue(); + } + + if (s != null) { + String str = s.toString(); + // ignore if this looks like an object reference + if (!str.matches(".*@[a-f0-9]+$")) { + return str; + } + } + return null; + } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/TextContentVariableContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/TextContentVariableContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/TextContentVariableContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/TextContentVariableContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/VoidVariableContextVariableTypeConverter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/VoidVariableContextVariableTypeConverter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/VoidVariableContextVariableTypeConverter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/contextvariables/converters/VoidVariableContextVariableTypeConverter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/AIException.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/AIException.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/AIException.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/AIException.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/ConfigurationException.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/ConfigurationException.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/ConfigurationException.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/ConfigurationException.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/SKCheckedException.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/SKCheckedException.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/SKCheckedException.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/SKCheckedException.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/SKException.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/SKException.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/SKException.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/exceptions/SKException.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokedEvent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokedEvent.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokedEvent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokedEvent.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokingEvent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokingEvent.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokingEvent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokingEvent.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHook.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHook.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHook.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHook.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHookEvent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHookEvent.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHookEvent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHookEvent.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHooks.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHooks.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHooks.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHooks.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PostChatCompletionEvent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PostChatCompletionEvent.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PostChatCompletionEvent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PostChatCompletionEvent.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreChatCompletionEvent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreChatCompletionEvent.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreChatCompletionEvent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreChatCompletionEvent.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreToolCallEvent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreToolCallEvent.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreToolCallEvent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreToolCallEvent.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderedEvent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderedEvent.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderedEvent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderedEvent.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderingEvent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderingEvent.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderingEvent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderingEvent.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/CollectionUtil.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/CollectionUtil.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/CollectionUtil.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/CollectionUtil.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/EmbeddedResourceLoader.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/EmbeddedResourceLoader.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/EmbeddedResourceLoader.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/EmbeddedResourceLoader.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/ServiceLoadUtil.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/ServiceLoadUtil.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/ServiceLoadUtil.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/ServiceLoadUtil.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/Todo.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/Todo.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/Todo.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/Todo.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/Verify.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/Verify.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/Verify.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/Verify.java diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/chatcompletion/ChatPromptParseVisitor.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/chatcompletion/ChatPromptParseVisitor.java new file mode 100644 index 00000000..f7b3e068 --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/chatcompletion/ChatPromptParseVisitor.java @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.implementation.chatcompletion; + +import com.azure.core.util.BinaryData; +import javax.annotation.Nullable; + +public interface ChatPromptParseVisitor { + + ChatPromptParseVisitor addMessage(String role, String content); + + ChatPromptParseVisitor addFunction(String name, @Nullable String description, + @Nullable BinaryData parameters); + + boolean areMessagesEmpty(); + + ChatPromptParseVisitor fromRawPrompt(String rawPrompt); + + T get(); + + ChatPromptParseVisitor reset(); +} \ No newline at end of file diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/XMLPromptParser.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/chatcompletion/ChatXMLPromptParser.java similarity index 66% rename from java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/XMLPromptParser.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/chatcompletion/ChatXMLPromptParser.java index c60b4a90..1c8f78c6 100644 --- a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/XMLPromptParser.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/chatcompletion/ChatXMLPromptParser.java @@ -1,31 +1,22 @@ // Copyright (c) Microsoft. All rights reserved. -package com.microsoft.semantickernel.aiservices.openai.chatcompletion; +package com.microsoft.semantickernel.implementation.chatcompletion; -import com.azure.ai.openai.models.ChatRequestAssistantMessage; -import com.azure.ai.openai.models.ChatRequestFunctionMessage; -import com.azure.ai.openai.models.ChatRequestMessage; -import com.azure.ai.openai.models.ChatRequestSystemMessage; -import com.azure.ai.openai.models.ChatRequestToolMessage; -import com.azure.ai.openai.models.ChatRequestUserMessage; -import com.azure.ai.openai.models.FunctionDefinition; import com.azure.core.util.BinaryData; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.microsoft.semantickernel.exceptions.SKException; import com.microsoft.semantickernel.orchestration.ToolCallBehavior; -import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.UUID; +import javax.annotation.Nullable; import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; @@ -33,47 +24,44 @@ import javax.xml.stream.events.Attribute; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; -import org.apache.commons.text.StringEscapeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class XMLPromptParser { +public class ChatXMLPromptParser { - private static final Logger LOGGER = LoggerFactory.getLogger(XMLPromptParser.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ChatXMLPromptParser.class); - public static ParsedPrompt parse(String rawPrompt) { + public static ChatPromptParseVisitor parse( + String rawPrompt, + ChatPromptParseVisitor chatPromptParseVisitor) { List prompts = Arrays.asList( rawPrompt, "" + rawPrompt + ""); for (String prompt : prompts) { try { - List parsedMessages = getChatRequestMessages(prompt); - List parsedFunctions = getFunctionDefinitions(prompt); + chatPromptParseVisitor = getChatRequestMessages(prompt, chatPromptParseVisitor); + chatPromptParseVisitor = getFunctionDefinitions(prompt, chatPromptParseVisitor); - if (!parsedMessages.isEmpty()) { - return new ParsedPrompt(parsedMessages, parsedFunctions); + if (!chatPromptParseVisitor.areMessagesEmpty()) { + return chatPromptParseVisitor; } } catch (SKException e) { //ignore + chatPromptParseVisitor = chatPromptParseVisitor.reset(); } } - ChatRequestUserMessage message = new ChatRequestUserMessage(rawPrompt); - - if (message.getName() == null) { - message.setName(UUID.randomUUID().toString()); - } - - return new ParsedPrompt(Collections.singletonList(message), null); + return chatPromptParseVisitor.fromRawPrompt(rawPrompt); } - private static List getChatRequestMessages(String prompt) { + private static ChatPromptParseVisitor getChatRequestMessages(String prompt, + ChatPromptParseVisitor chatPromptParseVisitor) { // TODO: XML parsing should be done as a chain of XMLEvent handlers. // If one handler does not recognize the element, it should pass it to the next handler. // In this way, we can avoid parsing the whole prompt twice and easily extend the parsing logic. - List messages = new ArrayList<>(); + try (InputStream is = new ByteArrayInputStream(prompt.getBytes(StandardCharsets.UTF_8))) { XMLInputFactory factory = XMLInputFactory.newInstance(); XMLEventReader reader = factory.createXMLEventReader(is); @@ -84,22 +72,41 @@ private static List getChatRequestMessages(String prompt) { if (name.equals("message")) { String role = getAttributeValue(event, "role"); String content = reader.getElementText(); - messages.add(getChatRequestMessage(role, content)); + chatPromptParseVisitor = chatPromptParseVisitor.addMessage(role, content); } } } } catch (IOException | XMLStreamException | IllegalArgumentException e) { throw new SKException("Failed to parse messages"); } - return messages; + return chatPromptParseVisitor; + } + + private static class FunctionDefinition { + + private final String name; + private final String description; + @Nullable + private BinaryData parameters; + + public FunctionDefinition(String name, String description) { + this.name = name; + this.description = description; + this.parameters = null; + } + + public void setParameters(BinaryData binaryData) { + this.parameters = binaryData; + } } - private static List getFunctionDefinitions(String prompt) { + private static ChatPromptParseVisitor getFunctionDefinitions(String prompt, + ChatPromptParseVisitor chatPromptParseVisitor) { // TODO: XML parsing should be done as a chain of XMLEvent handlers. See previous remark. // // ... // - List functionDefinitions = new ArrayList<>(); + try (InputStream is = new ByteArrayInputStream(prompt.getBytes(StandardCharsets.UTF_8))) { XMLInputFactory factory = XMLInputFactory.newFactory(); XMLEventReader reader = factory.createXMLEventReader(is); @@ -119,8 +126,8 @@ private static List getFunctionDefinitions(String prompt) { String description = getAttributeValue(event, "description"); // name has to match '^[a-zA-Z0-9_-]{1,64}$' functionDefinition = new FunctionDefinition( - ToolCallBehavior.formFullFunctionName(pluginName, name)) - .setDescription(description); + ToolCallBehavior.formFullFunctionName(pluginName, name), + description); } else if (elementName.equals("parameter")) { String name = getAttributeValue(event, "name"); String type = getAttributeValue(event, "type").toLowerCase(Locale.ROOT); @@ -185,7 +192,10 @@ private static List getFunctionDefinitions(String prompt) { BinaryData binaryData = BinaryData.fromObject(jsonNode); functionDefinition.setParameters(binaryData); } - functionDefinitions.add(functionDefinition); + chatPromptParseVisitor = chatPromptParseVisitor.addFunction( + functionDefinition.name, + functionDefinition.description, + functionDefinition.parameters); functionDefinition = null; parameters.clear(); requiredParameters.clear(); @@ -195,7 +205,7 @@ private static List getFunctionDefinitions(String prompt) { } catch (IOException | XMLStreamException | IllegalArgumentException e) { LOGGER.error("Error parsing prompt", e); } - return functionDefinitions; + return chatPromptParseVisitor; } private static String getElementName(XMLEvent xmlEvent) { @@ -217,57 +227,4 @@ private static String getAttributeValue(XMLEvent xmlEvent, String attributeName) // TODO: programmer's error - log at debug return ""; } - - private static ChatRequestMessage getChatRequestMessage( - String role, - String content) { - try { - AuthorRole authorRole = AuthorRole.valueOf(role.toUpperCase(Locale.ROOT)); - return OpenAIChatCompletion.getChatRequestMessage(authorRole, content); - } catch (IllegalArgumentException e) { - LOGGER.debug("Unknown author role: " + role); - throw new SKException("Unknown author role: " + role); - } - } - - public static ChatRequestMessage unescapeRequest(ChatRequestMessage message) { - if (message instanceof ChatRequestUserMessage) { - ChatRequestUserMessage chatRequestMessage = (ChatRequestUserMessage) message; - String content = StringEscapeUtils.unescapeXml( - chatRequestMessage.getContent().toString()); - - return new ChatRequestUserMessage(content) - .setName(chatRequestMessage.getName()); - } else if (message instanceof ChatRequestSystemMessage) { - ChatRequestSystemMessage chatRequestMessage = (ChatRequestSystemMessage) message; - String content = StringEscapeUtils.unescapeXml(chatRequestMessage.getContent()); - - return new ChatRequestSystemMessage(content) - .setName(chatRequestMessage.getName()); - } else if (message instanceof ChatRequestAssistantMessage) { - ChatRequestAssistantMessage chatRequestMessage = (ChatRequestAssistantMessage) message; - String content = StringEscapeUtils.unescapeXml(chatRequestMessage.getContent()); - - return new ChatRequestAssistantMessage(content) - .setToolCalls(chatRequestMessage.getToolCalls()) - .setFunctionCall(chatRequestMessage.getFunctionCall()) - .setName(chatRequestMessage.getName()); - } else if (message instanceof ChatRequestFunctionMessage) { - ChatRequestFunctionMessage chatRequestMessage = (ChatRequestFunctionMessage) message; - String content = StringEscapeUtils.unescapeXml(chatRequestMessage.getContent()); - - return new ChatRequestFunctionMessage( - chatRequestMessage.getName(), - content); - } else if (message instanceof ChatRequestToolMessage) { - ChatRequestToolMessage chatRequestMessage = (ChatRequestToolMessage) message; - String content = StringEscapeUtils.unescapeXml(chatRequestMessage.getContent()); - - return new ChatRequestToolMessage( - content, - chatRequestMessage.getToolCallId()); - } - - throw new SKException("Unknown message type: " + message.getClass().getSimpleName()); - } } \ No newline at end of file diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/telemetry/SemanticKernelTelemetry.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/telemetry/SemanticKernelTelemetry.java new file mode 100644 index 00000000..2ba4b846 --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/telemetry/SemanticKernelTelemetry.java @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.implementation.telemetry; + +import com.azure.ai.openai.models.CompletionsUsage; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.StatusCode; +import javax.annotation.Nullable; + +public class SemanticKernelTelemetry { + + public static final String OPEN_AI_PROVIDER = "openai"; + + public static Span startChatCompletionSpan( + @Nullable String modelName, + String modelProvider, + @Nullable Integer maxTokens, + @Nullable Double temperature, + @Nullable Double topP) { + return startCompletionSpan("chat.completions", modelName, modelProvider, maxTokens, + temperature, topP); + } + + public static Span startTextCompletionSpan( + @Nullable String modelName, + String modelProvider, + @Nullable Integer maxTokens, + @Nullable Double temperature, + @Nullable Double topP) { + return startCompletionSpan("text.completions", modelName, modelProvider, maxTokens, + temperature, topP); + } + + private static Span startCompletionSpan( + String operationName, + @Nullable String modelName, + String modelProvider, + @Nullable Integer maxTokens, + @Nullable Double temperature, + @Nullable Double topP) { + OpenTelemetry otel = GlobalOpenTelemetry.get(); + + if (modelName == null) { + modelName = "unknown"; + } + SpanBuilder builder = otel + .getTracer("SemanticKernel") + .spanBuilder(operationName + " " + modelName) + .setAttribute("gen_ai.request.model", modelName) + .setAttribute("gen_ai.operation.name", operationName) + .setAttribute("gen_ai.system", modelProvider); + + if (maxTokens != null) { + builder.setAttribute("gen_ai.request.max_tokens", maxTokens); + } + if (temperature != null) { + builder.setAttribute("gen_ai.request.temperature", temperature); + } + if (topP != null) { + builder.setAttribute("gen_ai.request.top_p", topP); + } + + return builder.startSpan(); + } + + public static void endSpanWithUsage(Span span, CompletionsUsage usage) { + span.setStatus(StatusCode.OK); + span.setAttribute("gen_ai.response.completion_tokens", usage.getCompletionTokens()); + span.setAttribute("gen_ai.response.prompt_tokens", usage.getPromptTokens()); + span.end(); + } + + public static void endSpanWithError(Span span) { + span.setStatus(StatusCode.ERROR); + span.end(); + } +} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/CodeTokenizer.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/CodeTokenizer.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/CodeTokenizer.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/CodeTokenizer.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/DefaultPromptTemplate.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/DefaultPromptTemplate.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/DefaultPromptTemplate.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/DefaultPromptTemplate.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/TemplateTokenizer.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/TemplateTokenizer.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/TemplateTokenizer.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/TemplateTokenizer.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/Block.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/Block.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/Block.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/Block.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/BlockTypes.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/BlockTypes.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/BlockTypes.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/BlockTypes.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeBlock.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeBlock.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeBlock.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeBlock.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeRendering.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeRendering.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeRendering.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeRendering.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/FunctionIdBlock.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/FunctionIdBlock.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/FunctionIdBlock.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/FunctionIdBlock.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/NamedArgBlock.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/NamedArgBlock.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/NamedArgBlock.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/NamedArgBlock.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/Symbols.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/Symbols.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/Symbols.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/Symbols.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/TextBlock.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/TextBlock.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/TextBlock.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/TextBlock.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/TextRendering.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/TextRendering.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/TextRendering.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/TextRendering.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/ValBlock.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/ValBlock.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/ValBlock.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/ValBlock.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/VarBlock.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/VarBlock.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/VarBlock.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/VarBlock.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionInvocation.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionInvocation.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionInvocation.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionInvocation.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResult.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResult.java similarity index 94% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResult.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResult.java index 359598c8..510bb65f 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResult.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResult.java @@ -16,7 +16,7 @@ public class FunctionResult { private final ContextVariable result; - private final FunctionResultMetadata metadata; + private final FunctionResultMetadata metadata; @Nullable private final Object unconvertedResult; @@ -30,7 +30,7 @@ public class FunctionResult { */ public FunctionResult( ContextVariable result, - @Nullable FunctionResultMetadata metadata, + @Nullable FunctionResultMetadata metadata, @Nullable Object unconvertedResult) { this.result = result; this.metadata = metadata == null ? FunctionResultMetadata.empty() : metadata; @@ -77,7 +77,7 @@ public ContextVariable getResultVariable() { * * @return The metadata about the result of the function invocation. */ - public FunctionResultMetadata getMetadata() { + public FunctionResultMetadata getMetadata() { return metadata; } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResultMetadata.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResultMetadata.java similarity index 83% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResultMetadata.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResultMetadata.java index 9d9612c3..f9e79c7f 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResultMetadata.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResultMetadata.java @@ -1,9 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.orchestration; -import com.azure.ai.openai.models.CompletionsUsage; import com.microsoft.semantickernel.contextvariables.CaseInsensitiveMap; import com.microsoft.semantickernel.contextvariables.ContextVariable; +import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter; + import java.time.OffsetDateTime; import javax.annotation.Nullable; @@ -12,7 +13,7 @@ *

* This class is used to return metadata about the result of a function invocation. */ -public class FunctionResultMetadata { +public class FunctionResultMetadata { /** * The key for id metadata. @@ -50,7 +51,7 @@ public FunctionResultMetadata(CaseInsensitiveMap> metadata) { /** * Create a new instance of FunctionResultMetadata. */ - public static FunctionResultMetadata build(String id) { + public static FunctionResultMetadata build(String id) { return build(id, null, null); } @@ -62,21 +63,24 @@ public static FunctionResultMetadata build(String id) { * @param createdAt The time the result was created. * @return A new instance of FunctionResultMetadata. */ - public static FunctionResultMetadata build( + public static FunctionResultMetadata build( String id, - @Nullable CompletionsUsage usage, + @Nullable UsageType usage, @Nullable OffsetDateTime createdAt) { CaseInsensitiveMap> metadata = new CaseInsensitiveMap<>(); + metadata.put(ID, ContextVariable.of(id)); + if (usage != null) { - metadata.put(USAGE, ContextVariable.of(usage)); + metadata.put(USAGE, ContextVariable.of(usage, + new ContextVariableTypeConverter.NoopConverter<>(Object.class))); } if (createdAt != null) { metadata.put(CREATED_AT, ContextVariable.of(createdAt)); } - return new FunctionResultMetadata(metadata); + return new FunctionResultMetadata<>(metadata); } /** @@ -84,8 +88,8 @@ public static FunctionResultMetadata build( * * @return A new instance of FunctionResultMetadata. */ - public static FunctionResultMetadata empty() { - return new FunctionResultMetadata(new CaseInsensitiveMap<>()); + public static FunctionResultMetadata empty() { + return new FunctionResultMetadata<>(new CaseInsensitiveMap<>()); } /** @@ -117,12 +121,12 @@ public String getId() { * @return The usage of the result of the function invocation. */ @Nullable - public CompletionsUsage getUsage() { + public UsageType getUsage() { ContextVariable usage = metadata.get(USAGE); if (usage == null) { return null; } - return usage.getValue(CompletionsUsage.class); + return (UsageType) usage.getValue(Object.class); } /** diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationContext.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationContext.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationContext.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationContext.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationReturnMode.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationReturnMode.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationReturnMode.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationReturnMode.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettings.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettings.java similarity index 99% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettings.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettings.java index 10b2079f..12dc0bbc 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettings.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettings.java @@ -550,7 +550,7 @@ public Builder withTokenSelectionBiases(Map tokenSelectionBias */ public Builder withResponseFormat(ResponseFormat responseFormat) { if (responseFormat != null) { - settings.put(RESPONSE_FORMAT, responseFormat); + settings.put(RESPONSE_FORMAT, responseFormat.toString()); } return this; } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ResponseFormat.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ResponseFormat.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ResponseFormat.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ResponseFormat.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ToolCallBehavior.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ToolCallBehavior.java similarity index 99% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ToolCallBehavior.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ToolCallBehavior.java index 8148eb16..affa13ac 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ToolCallBehavior.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ToolCallBehavior.java @@ -70,7 +70,7 @@ public static ToolCallBehavior allowOnlyKernelFunctions(boolean autoInvoke, } private static final int DEFAULT_MAXIMUM_AUTO_INVOKE_ATTEMPTS = 5; - private static final String FUNCTION_NAME_SEPARATOR = "-"; + public static final String FUNCTION_NAME_SEPARATOR = "-"; private final int maximumAutoInvokeAttempts; /** diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPlugin.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPlugin.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPlugin.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPlugin.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginFactory.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginFactory.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginFactory.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginFactory.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/AggregatorPromptTemplateFactory.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/AggregatorPromptTemplateFactory.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/AggregatorPromptTemplateFactory.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/AggregatorPromptTemplateFactory.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/HandlebarsPromptTemplateFactory.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/HandlebarsPromptTemplateFactory.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/HandlebarsPromptTemplateFactory.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/HandlebarsPromptTemplateFactory.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputConfig.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputConfig.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputConfig.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputConfig.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputParameter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputParameter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputParameter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputParameter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputVariable.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputVariable.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputVariable.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/InputVariable.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunction.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunction.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunction.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunction.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionArguments.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionArguments.java similarity index 98% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionArguments.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionArguments.java index ecf39ef8..f3b46c21 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionArguments.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionArguments.java @@ -295,6 +295,9 @@ public Builder withVariable(String key, ContextVariable value) { * @throws SKException if the value cannot be converted to a ContextVariable */ public Builder withVariable(String key, Object value) { + if (value instanceof ContextVariable) { + return withVariable(key, (ContextVariable) value); + } return withVariable(key, ContextVariable.ofGlobalType(value)); } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethod.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethod.java similarity index 83% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethod.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethod.java index 77ba8318..6d9d1166 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethod.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethod.java @@ -147,7 +147,7 @@ private static MethodDetails getMethodDetails( } @SuppressWarnings("unchecked") - private static ImplementationFunc getFunction(Method method, Object instance) { + public static ImplementationFunc getFunction(Method method, Object instance) { return (kernel, function, arguments, variableType, invocationContext) -> { InvocationContext context; if (invocationContext == null) { @@ -354,22 +354,190 @@ private static Object getArgumentValue( Parameter parameter, Kernel kernel, InvocationContext invocationContext) { + // The actual type of the method argument + Class targetArgType = parameter.getType(); + + if (Kernel.class.isAssignableFrom(targetArgType)) { + return kernel; + } + String variableName = getGetVariableName(parameter); + ContextVariable variable = getVariableFromContext(method, context, variableName); + + // The Value of what has been provided + Object sourceValue = null; + ContextVariableType sourceType = null; + + // if needed extract the default value from annotation and substitute it for the missing variable + variable = extractDefaultTypeFromAnnotation( + method, + parameter, + invocationContext, + variable, + sourceValue, + variableName); + + if (variable != null) { + sourceValue = variable.getValue(); + sourceType = variable.getType(); + } + + Class requestedType = targetArgType; + + KernelFunctionParameter annotation = parameter.getAnnotation(KernelFunctionParameter.class); + if (annotation != null && annotation.type() != null) { + requestedType = annotation.type(); + } + + // Ignore string as there is a good chance the developer user left it to default + if (requestedType != String.class && !targetArgType.isAssignableFrom(requestedType)) { + throw new AIException( + AIException.ErrorCodes.INVALID_CONFIGURATION, + "Annotation on method: " + method.getName() + " requested conversion to type: " + + requestedType.getName() + + ", however this cannot be assigned to parameter of type: " + + targetArgType); + } + + if (requestedType == String.class && !targetArgType.isAssignableFrom(requestedType)) { + LOGGER.warn( + "Annotation on method: {} is requesting a String which is not assignable to method type {}, possibly as the type argument has not been provided on the annotation.", + method.getName(), + targetArgType.getName()); + } + + // First try to convert to the type requested from the annotation + Object converted = toObjectType( + parameter.getName(), + sourceType, + sourceValue, + requestedType, + invocationContext); + + if (targetArgType.isInstance(converted)) { + return converted; + } + + if (converted != null) { + // Could not convert to requested type, try to target type + ContextVariable convertedCv = invocationContext + .getContextVariableTypes() + .contextVariableOf(converted); + + converted = toObjectType( + parameter.getName(), + convertedCv.getType(), + convertedCv, + targetArgType, + invocationContext); + } + + if (targetArgType.isInstance(converted)) { + return converted; + } + + // Could not convert to requested type, try to target type + return toObjectType( + parameter.getName(), + sourceType, + sourceValue, + targetArgType, + invocationContext); + } + + @Nullable + private static Object toObjectType( + String parameterName, + + @Nullable ContextVariableType sourceType, + @Nullable Object sourceValue, + + Class targetArgType, + InvocationContext invocationContext) { + + if (sourceType != null) { + + if (targetArgType.isAssignableFrom(sourceType.getClazz())) { + return sourceValue; + } + + if (isPrimitive(sourceType.getClazz(), targetArgType)) { + return sourceValue; + } + + ContextVariableTypeConverter c = sourceType.getConverter(); + + Object converted = c.toObject(invocationContext.getContextVariableTypes(), sourceValue, + targetArgType); + if (converted != null) { + return converted; + } + } + + // Well-known types only + ContextVariableType converter = invocationContext.getContextVariableTypes() + .getVariableTypeForClass(targetArgType); + if (converter != null) { + try { + Object converted = converter.getConverter().fromObject(sourceValue); + if (converted != null) { + return converted; + } + } catch (NumberFormatException nfe) { + throw new AIException( + ErrorCodes.INVALID_CONFIGURATION, + "Invalid value for " + + parameterName + + " expected " + + targetArgType.getSimpleName() + + " but got " + + sourceValue); + } + } + + // If doing a type conversion fails, and we are going to a string, try using toPromptString + if (sourceType != null && targetArgType.equals(String.class)) { + ContextVariableTypeConverter c = sourceType.getConverter(); - ContextVariable arg = context == null ? null : context.get(variableName); + Object converted = c.toPromptString(invocationContext.getContextVariableTypes(), + sourceValue); + if (converted != null) { + return converted; + } + } + + return null; + } + + @Nullable + private static ContextVariable getVariableFromContext( + Method method, + @Nullable KernelFunctionArguments context, + String variableName) { + ContextVariable variable = context == null ? null : context.get(variableName); // If there is 1 argument use "input" or the only argument - if (arg == null && method.getParameters().length == 1) { + if (variable == null && method.getParameters().length == 1) { if (context != null) { if (context.containsKey(KernelFunctionArguments.MAIN_KEY)) { - arg = context.get(KernelFunctionArguments.MAIN_KEY); + variable = context.get(KernelFunctionArguments.MAIN_KEY); } else if (context.size() == 1) { - arg = context.values().iterator().next(); + variable = context.values().iterator().next(); } } } + return variable; + } - if (arg == null) { + @Nullable + private static ContextVariable extractDefaultTypeFromAnnotation( + Method method, + Parameter parameter, + InvocationContext invocationContext, + @Nullable ContextVariable variable, + @Nullable Object sourceValue, + String variableName) { + if (variable == null) { KernelFunctionParameter annotation = parameter .getAnnotation(KernelFunctionParameter.class); if (annotation != null) { @@ -385,17 +553,17 @@ private static Object getArgumentValue( String defaultValue = annotation.defaultValue(); Object value = cvType.getConverter().fromPromptString(defaultValue); - arg = ContextVariable.convert(value, type, + variable = ContextVariable.convert(value, type, invocationContext.getContextVariableTypes()); } - if (arg != null && NO_DEFAULT_VALUE.equals(arg.getValue())) { + if (variable != null && NO_DEFAULT_VALUE.equals(sourceValue)) { if (!annotation.required()) { return null; } throw new AIException( - AIException.ErrorCodes.INVALID_CONFIGURATION, + ErrorCodes.INVALID_CONFIGURATION, "Attempted to invoke function " + method.getDeclaringClass().getName() + "." @@ -408,11 +576,11 @@ private static Object getArgumentValue( } } - if (arg == null && variableName.matches("arg\\d")) { + if (variable == null && variableName.matches("arg\\d")) { LOGGER.warn(formErrorMessage(method, parameter)); } - if (arg != null && NO_DEFAULT_VALUE.equals(arg.getValue())) { + if (variable != null && NO_DEFAULT_VALUE.equals(sourceValue)) { if (parameter.getName().matches("arg\\d")) { throw new AIException( AIException.ErrorCodes.INVALID_CONFIGURATION, @@ -420,75 +588,11 @@ private static Object getArgumentValue( } else { throw new AIException( AIException.ErrorCodes.INVALID_CONFIGURATION, - "Unknown arg " + parameter.getName()); + "Unknown variable " + parameter.getName()); } } - if (Kernel.class.isAssignableFrom(parameter.getType())) { - return kernel; - } - - KernelFunctionParameter annotation = parameter.getAnnotation(KernelFunctionParameter.class); - if (annotation == null || annotation.type() == null) { - return arg; - } - - Class type = annotation.type(); - - if (!parameter.getType().isAssignableFrom(type)) { - throw new AIException( - AIException.ErrorCodes.INVALID_CONFIGURATION, - "Annotation on method: " + method.getName() + " requested conversion to type: " - + type.getName() + ", however this cannot be assigned to parameter of type: " - + parameter.getType()); - } - - Object value = arg; - - if (arg != null) { - - if (parameter.getType().isAssignableFrom(arg.getType().getClazz())) { - return arg.getValue(); - } - - if (isPrimitive(arg.getType().getClazz(), parameter.getType())) { - return arg.getValue(); - } - - ContextVariableTypeConverter c = arg.getType().getConverter(); - - Object converted = c.toObject(invocationContext.getContextVariableTypes(), - arg.getValue(), parameter.getType()); - if (converted != null) { - return converted; - } - } - - // Well-known types only - ContextVariableType converter = invocationContext.getContextVariableTypes() - .getVariableTypeForClass(type); - if (converter != null) { - try { - value = converter.getConverter().fromObject(arg); - } catch (NumberFormatException nfe) { - throw new AIException( - AIException.ErrorCodes.INVALID_CONFIGURATION, - "Invalid value for " - + parameter.getName() - + " expected " - + type.getSimpleName() - + " but got " - + arg); - } - } - - if (value == null && type.equals(String.class) && arg != null) { - ContextVariableTypeConverter c = arg.getType().getConverter(); - - value = c.toPromptString(invocationContext.getContextVariableTypes(), arg.getValue()); - } - - return value; + return variable; } @SuppressWarnings("OperatorPrecedence") diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromPrompt.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromPrompt.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromPrompt.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromPrompt.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionMetadata.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionMetadata.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionMetadata.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionMetadata.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionYaml.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionYaml.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionYaml.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionYaml.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelPromptTemplateFactory.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelPromptTemplateFactory.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelPromptTemplateFactory.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelPromptTemplateFactory.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/MethodDetails.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/MethodDetails.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/MethodDetails.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/MethodDetails.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/OutputVariable.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/OutputVariable.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/OutputVariable.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/OutputVariable.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplate.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplate.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplate.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplate.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfig.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfig.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfig.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfig.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateFactory.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateFactory.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateFactory.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateFactory.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/DefineKernelFunction.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/DefineKernelFunction.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/DefineKernelFunction.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/DefineKernelFunction.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/KernelFunctionParameter.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/KernelFunctionParameter.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/KernelFunctionParameter.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/KernelFunctionParameter.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/SKSample.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/SKSample.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/SKSample.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/annotations/SKSample.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIService.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIService.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIService.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIService.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceCollection.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceCollection.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceCollection.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceCollection.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelection.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelection.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelection.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelection.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelector.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelector.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelector.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelector.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/BaseAIServiceSelector.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/BaseAIServiceSelector.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/BaseAIServiceSelector.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/BaseAIServiceSelector.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/KernelContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/KernelContent.java similarity index 89% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/KernelContent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/KernelContent.java index 6e85f34e..e55f2cdc 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/KernelContent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/KernelContent.java @@ -57,16 +57,6 @@ public T getInnerContent() { return innerContent; } - /** - * Gets the model identifier used to generate the content. - * - * @return The model identifier used to generate the content. - */ - @Nullable - public String getModelId() { - return modelId; - } - /** * Gets the metadata associated with the content. * diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/NamedServiceProvider.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/NamedServiceProvider.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/NamedServiceProvider.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/NamedServiceProvider.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/ServiceNotFoundException.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/ServiceNotFoundException.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/ServiceNotFoundException.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/ServiceNotFoundException.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/StreamingKernelContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/StreamingKernelContent.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/StreamingKernelContent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/StreamingKernelContent.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/TextAIService.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/TextAIService.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/TextAIService.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/TextAIService.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioContent.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioContent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioContent.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioToTextExecutionSettings.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioToTextExecutionSettings.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioToTextExecutionSettings.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioToTextExecutionSettings.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioToTextService.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioToTextService.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioToTextService.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/AudioToTextService.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/TextToAudioExecutionSettings.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/TextToAudioExecutionSettings.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/TextToAudioExecutionSettings.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/TextToAudioExecutionSettings.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/TextToAudioService.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/TextToAudioService.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/TextToAudioService.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/audio/TextToAudioService.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/AuthorRole.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/AuthorRole.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/AuthorRole.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/AuthorRole.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatCompletionService.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatCompletionService.java similarity index 68% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatCompletionService.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatCompletionService.java index df653841..0e566f3d 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatCompletionService.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatCompletionService.java @@ -15,19 +15,6 @@ * Chat completion service interface. */ public interface ChatCompletionService extends TextAIService { - - /** - * Get a builder for creating a {@code ChatCompletionService}. The builder loads a service that - * implements the {@link ChatCompletionService.Builder} interface. - * - * @return a builder for creating a {@code ChatCompletionService} - */ - static Builder builder() { - return ServiceLoadUtil.findServiceLoader(Builder.class, - "com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion$Builder") - .get(); - } - /** * Gets the chat message contents asynchronously using {@code ChatHistory} to support a * turn-based conversation. Typically, the resulting chat message contents is appended to the @@ -55,14 +42,4 @@ Mono>> getChatMessageContentsAsync( String prompt, @Nullable Kernel kernel, @Nullable InvocationContext invocationContext); - - /** - * Builder API for creating a {@link ChatCompletionService}. Concrete implementations of - * {@link ChatCompletionService} must implement the {@link SemanticKernelBuilder#build()} - * method. - */ - abstract class Builder extends OpenAiServiceBuilder { - - } - } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatHistory.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatHistory.java similarity index 89% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatHistory.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatHistory.java index a0a1a43b..d2f391ff 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatHistory.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatHistory.java @@ -2,6 +2,7 @@ package com.microsoft.semantickernel.services.chatcompletion; import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; +import com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageTextContent; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; @@ -35,9 +36,7 @@ public ChatHistory(@Nullable String instructions) { this.chatMessageContents = new ArrayList<>(); if (instructions != null) { this.chatMessageContents.add( - new ChatMessageContent<>( - AuthorRole.SYSTEM, - instructions)); + ChatMessageTextContent.systemMessage(instructions)); } } @@ -117,7 +116,12 @@ public Spliterator> spliterator() { public void addMessage(AuthorRole authorRole, String content, Charset encoding, FunctionResultMetadata metadata) { chatMessageContents.add( - new ChatMessageContent<>(authorRole, content, null, null, encoding, metadata)); + ChatMessageTextContent.builder() + .withAuthorRole(authorRole) + .withContent(content) + .withEncoding(encoding) + .withMetadata(metadata) + .build()); } /** @@ -128,7 +132,10 @@ public void addMessage(AuthorRole authorRole, String content, Charset encoding, */ public void addMessage(AuthorRole authorRole, String content) { chatMessageContents.add( - new ChatMessageContent<>(authorRole, content, null, null, null, null)); + ChatMessageTextContent.builder() + .withAuthorRole(authorRole) + .withContent(content) + .build()); } /** diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java similarity index 64% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java index c75c8d2e..21535791 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java @@ -3,6 +3,7 @@ import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; import com.microsoft.semantickernel.services.KernelContent; +import com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageContentType; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -12,6 +13,11 @@ /** * Represents the content of a chat message + *

+ * This class defaults to a {@link ChatMessageContentType#TEXT} content type if none is specified. + * However, if using this for text content, consider using + * {@link com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageTextContent} and + * its builders instead. * * @param the type of the inner content within the messages */ @@ -24,9 +30,11 @@ public class ChatMessageContent extends KernelContent { private final List> items; @Nullable private final Charset encoding; + private final ChatMessageContentType contentType; /** - * Creates a new instance of the {@link ChatMessageContent} class. + * Creates a new instance of the {@link ChatMessageContent} class. Defaults to + * {@link ChatMessageContentType#TEXT} content type. * * @param authorRole the author role that generated the content * @param content the content @@ -44,7 +52,8 @@ public ChatMessageContent( } /** - * Creates a new instance of the {@link ChatMessageContent} class. + * Creates a new instance of the {@link ChatMessageContent} class. Defaults to + * {@link ChatMessageContentType#TEXT} content type. * * @param authorRole the author role that generated the content * @param content the content @@ -60,11 +69,35 @@ public ChatMessageContent( @Nullable T innerContent, @Nullable Charset encoding, @Nullable FunctionResultMetadata metadata) { + this(authorRole, content, modelId, innerContent, encoding, metadata, + ChatMessageContentType.TEXT); + } + + /** + * Creates a new instance of the {@link ChatMessageContent} class. + * + * @param authorRole the author role that generated the content + * @param content the content + * @param modelId the model id + * @param innerContent the inner content + * @param encoding the encoding + * @param metadata the metadata + * @param contentType the content type + */ + public ChatMessageContent( + AuthorRole authorRole, + String content, + @Nullable String modelId, + @Nullable T innerContent, + @Nullable Charset encoding, + @Nullable FunctionResultMetadata metadata, + ChatMessageContentType contentType) { super(innerContent, modelId, metadata); this.authorRole = authorRole; this.content = content; this.encoding = encoding != null ? encoding : StandardCharsets.UTF_8; this.items = null; + this.contentType = contentType; } /** @@ -76,19 +109,26 @@ public ChatMessageContent( * @param innerContent the inner content * @param encoding the encoding * @param metadata the metadata + * @param contentType the content type */ public ChatMessageContent( AuthorRole authorRole, - List> items, + @Nullable List> items, String modelId, T innerContent, Charset encoding, - FunctionResultMetadata metadata) { + FunctionResultMetadata metadata, + ChatMessageContentType contentType) { super(innerContent, modelId, metadata); this.content = null; this.authorRole = authorRole; this.encoding = encoding != null ? encoding : StandardCharsets.UTF_8; - this.items = new ArrayList<>(items); + if (items == null) { + this.items = null; + } else { + this.items = new ArrayList<>(items); + } + this.contentType = contentType; } /** @@ -116,7 +156,11 @@ public String getContent() { * * @return the items, which may be {@code null} */ + @Nullable public List> getItems() { + if (items == null) { + return null; + } return Collections.unmodifiableList(items); } @@ -130,8 +174,18 @@ public Charset getEncoding() { return encoding; } + /** + * Gets the content type + * + * @return the content type + */ + public ChatMessageContentType getContentType() { + return contentType; + } + @Override public String toString() { return content != null ? content : ""; } + } diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/message/ChatMessageContentType.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/message/ChatMessageContentType.java new file mode 100644 index 00000000..2845deb0 --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/message/ChatMessageContentType.java @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.services.chatcompletion.message; + +/** + * Represents the content of a chat message + */ +public enum ChatMessageContentType { + /** + * The content is text + */ + TEXT, + /** + * The content is an image + */ + IMAGE_URL +} diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/message/ChatMessageImageContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/message/ChatMessageImageContent.java new file mode 100644 index 00000000..e87f7fcc --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/message/ChatMessageImageContent.java @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.services.chatcompletion.message; + +import com.microsoft.semantickernel.builders.SemanticKernelBuilder; +import com.microsoft.semantickernel.exceptions.SKException; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import java.net.URL; +import java.util.Base64; +import javax.annotation.Nullable; + +public class ChatMessageImageContent extends ChatMessageContent { + + private final ImageDetail detail; + + public ChatMessageImageContent( + String content, + @Nullable String modelId, + @Nullable ImageDetail detail) { + super( + AuthorRole.USER, + content, + modelId, + null, + null, + null, + ChatMessageContentType.IMAGE_URL); + + if (detail == null) { + detail = ImageDetail.AUTO; + } + this.detail = detail; + } + + /** + * Get the detail level of the image to include in the chat message. + * + * @return the detail level of the image + */ + public ImageDetail getDetail() { + return detail; + } + + /** + * The detail level of the image to include in the chat message. + */ + public enum ImageDetail { + /** + * Low detail + */ + LOW, + /** + * High detail + */ + HIGH, + /** + * Automatically determine the detail level + */ + AUTO + } + + /** + * Create a new builder for the {@link ChatMessageImageContent} class. + * + * @param the type of the inner content within the messages + * @return a new builder + */ + public static Builder builder() { + return new Builder<>(); + } + + public static class Builder implements SemanticKernelBuilder> { + + @Nullable + private String modelId = null; + @Nullable + private String content = null; + @Nullable + private ImageDetail detail = null; + + /** + * Set the model ID to use for the chat message. + * + * @param modelId the model ID + * @return this builder + */ + public Builder withModelId(String modelId) { + this.modelId = modelId; + return this; + } + + /** + * @param imageType For instance jpg or png. For known types known to OpenAI see: docs. + * @param content the image content + * @return this builder + */ + public Builder withImage( + String imageType, + byte[] content) { + this.content = String.format("data:image/%s;base64,%s", + imageType, + Base64.getEncoder().encodeToString(content)); + return this; + } + + /** + * Set the URL of the image to include in the chat message. + * + * @param url the URL of the image + * @return this builder + */ + public Builder withImageUrl(String url) { + this.content = url; + return this; + } + + /** + * Set the URL of the image to include in the chat message. + * + * @param url the URL of the image + * @return this builder + */ + public Builder withImageUrl(URL url) { + this.content = url.toString(); + return this; + } + + /** + * Set the detail level of the image to include in the chat message. + * + * @param detail the detail level of the image + * @return this builder + */ + public Builder withDetail(ImageDetail detail) { + this.detail = detail; + return this; + } + + @Override + public ChatMessageImageContent build() { + if (detail == null) { + detail = ImageDetail.AUTO; + } + if (content == null) { + throw new SKException("Image content is required"); + } + return new ChatMessageImageContent<>( + content, + modelId, + detail); + } + } +} diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/message/ChatMessageTextContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/message/ChatMessageTextContent.java new file mode 100644 index 00000000..ccce7e4d --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/message/ChatMessageTextContent.java @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.services.chatcompletion.message; + +import com.microsoft.semantickernel.builders.SemanticKernelBuilder; +import com.microsoft.semantickernel.exceptions.SKException; +import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; +import com.microsoft.semantickernel.services.KernelContent; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import java.nio.charset.Charset; +import java.util.List; +import javax.annotation.Nullable; + +/** + * Represents the content of a chat message which contains text + */ +public class ChatMessageTextContent extends ChatMessageContent { + + /** + * Creates a new instance of the {@link ChatMessageTextContent} class. + * + * @param authorRole the author role that generated the content + * @param content the content + * @param modelId the model id + * @param encoding the encoding of the content + * @param metadata the metadata + */ + public ChatMessageTextContent( + AuthorRole authorRole, + String content, + @Nullable String modelId, + @Nullable Charset encoding, + @Nullable FunctionResultMetadata metadata) { + super(authorRole, content, modelId, null, encoding, metadata, + ChatMessageContentType.TEXT); + } + + /** + * Create a new builder for the {@link ChatMessageTextContent} class. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + private static ChatMessageTextContent buildContent(AuthorRole role, String content) { + return new Builder() + .withAuthorRole(role) + .withContent(content) + .build(); + } + + /** + * Create a message with the author role set to {@link AuthorRole#USER} + * + * @param content The content of the message + * @return The message + */ + public static ChatMessageTextContent userMessage(String content) { + return buildContent(AuthorRole.USER, content); + } + + /** + * Create a message with the author role set to {@link AuthorRole#ASSISTANT} + * + * @param content The content of the message + * @return The message + */ + public static ChatMessageTextContent assistantMessage(String content) { + return buildContent(AuthorRole.ASSISTANT, content); + } + + /** + * Create a message with the author role set to {@link AuthorRole#SYSTEM} + * + * @param content The content of the message + * @return The message + */ + public static ChatMessageTextContent systemMessage(String content) { + return buildContent(AuthorRole.SYSTEM, content); + } + + public static class Builder implements SemanticKernelBuilder { + + @Nullable + private String modelId = null; + @Nullable + private FunctionResultMetadata metadata = null; + @Nullable + private AuthorRole authorRole = null; + @Nullable + private String content = null; + @Nullable + private List> items = null; + @Nullable + private Charset encoding = null; + + /** + * Set the content of the message + * + * @param content The content of the message + * @return The builder + */ + public Builder withContent(String content) { + this.content = content; + return this; + } + + /** + * Set the model ID used to generate the content + * + * @param modelId The model ID + * @return The builder + */ + public Builder withModelId(String modelId) { + this.modelId = modelId; + return this; + } + + /** + * Set the metadata associated with the content + * + * @param metadata The metadata + * @return The builder + */ + public Builder withMetadata(FunctionResultMetadata metadata) { + this.metadata = metadata; + return this; + } + + /** + * Set the author role of the message + * + * @param authorRole The author role + * @return The builder + */ + public Builder withAuthorRole(AuthorRole authorRole) { + this.authorRole = authorRole; + return this; + } + + /** + * Set the encoding of the message + * + * @param encoding The encoding + * @return The builder + */ + public Builder withEncoding(Charset encoding) { + this.encoding = encoding; + return this; + } + + @Override + public ChatMessageTextContent build() { + if (authorRole == null) { + throw new SKException("Author role must be set"); + } + if (content == null) { + throw new SKException("Content must be set"); + } + return new ChatMessageTextContent( + authorRole, + content, + modelId, + encoding, + metadata); + } + } +} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/openai/OpenAiServiceBuilder.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/openai/OpenAiServiceBuilder.java similarity index 96% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/openai/OpenAiServiceBuilder.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/openai/OpenAiServiceBuilder.java index 7b9d7329..0197a55a 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/openai/OpenAiServiceBuilder.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/openai/OpenAiServiceBuilder.java @@ -67,6 +67,10 @@ public U withServiceId(String serviceId) { return (U) this; } + /** + * Builds the service. + * @return The service + */ @Override public abstract T build(); diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/StreamingTextContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/StreamingTextContent.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/StreamingTextContent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/StreamingTextContent.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/TextContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/TextContent.java similarity index 89% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/TextContent.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/TextContent.java index d5eb1690..50295a7c 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/TextContent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/TextContent.java @@ -3,6 +3,7 @@ import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; import com.microsoft.semantickernel.services.KernelContent; +import javax.annotation.Nullable; /** * Content from a text completion service. @@ -21,8 +22,8 @@ public class TextContent extends KernelContent { */ public TextContent( String content, - String modelId, - FunctionResultMetadata metadata) { + @Nullable String modelId, + @Nullable FunctionResultMetadata metadata) { super(content, modelId, metadata); this.content = content; } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/TextGenerationService.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/TextGenerationService.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/TextGenerationService.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textcompletion/TextGenerationService.java diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/Embedding.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/Embedding.java new file mode 100644 index 00000000..0a11e869 --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/Embedding.java @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.services.textembedding; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** Represents a strongly typed vector of numeric data. */ +public class Embedding { + + // vector is immutable! + private final List vector; + + private static final Embedding EMPTY = new Embedding(); + + public static Embedding empty() { + return EMPTY; + } + + /** Initializes a new instance of the Embedding class. */ + public Embedding() { + this.vector = Collections.emptyList(); + } + + /** + * Initializes a new instance of the Embedding class that contains numeric elements copied from + * the specified collection + * + * @param vector The collection whose elements are copied to the new Embedding + */ + public Embedding(@Nonnull List vector) { + Objects.requireNonNull(vector); + this.vector = Collections.unmodifiableList(vector); + } + + /** + * Initializes a new instance of the Embedding class that contains numeric elements copied from + * the specified array + * + * @param vector The array whose elements are copied to the new Embedding + */ + public Embedding(@Nonnull float[] vector) { + Objects.requireNonNull(vector); + List list = new ArrayList<>(vector.length); + for (float f : vector) { + list.add(f); + } + this.vector = Collections.unmodifiableList(list); + } + + /** + * Return the embedding vector as a read-only list. + * + * @return The embedding vector as a read-only list. + */ + public List getVector() { + return Collections.unmodifiableList(this.vector); + } + + /** + * Calculates the dot product of this {@code Embedding} with another. + * + * @param other The other {@code Embedding} to compute the dot product with + * @return The dot product between the {@code Embedding} vectors + */ + public float dot(@Nonnull Embedding other) { + Objects.requireNonNull(other); + return VectorOperations.dot(this.vector, other.getVector()); + } + + /** + * Calculates the Euclidean length of this vector. + * + * @return Euclidean length + */ + public float euclideanLength() { + return VectorOperations.euclideanLength(this.vector); + } + + /** + * Calculates the cosine similarity of this vector with another. + * + * @param other The other vector to compute cosine similarity with. + * @return Cosine similarity between vectors + */ + public float cosineSimilarity(@Nonnull Embedding other) { + Objects.requireNonNull(other); + return VectorOperations.cosineSimilarity(this.vector, other.getVector()); + } + + /** + * Multiply the {@code Embedding} vector by a multiplier. + * + * @param multiplier The multiplier to multiply the {@code Embedding} vector by + * @return A new {@code Embedding} with the vector multiplied by the multiplier + */ + public Embedding multiply(float multiplier) { + return new Embedding(VectorOperations.multiply(this.vector, multiplier)); + } + + /** + * Divide the {@code Embedding} vector by a divisor. + * + * @param divisor The divisor to divide the {@code Embedding} vector by + * @return A new {@code Embedding} with the vector divided by the divisor + */ + public Embedding divide(float divisor) { + return new Embedding(VectorOperations.divide(this.vector, divisor)); + } + + /** + * Normalizes the underlying vector, such that the Euclidean length is 1. + * + * @return A new {@code Embedding} with the normalized vector + */ + public Embedding normalize() { + return new Embedding(VectorOperations.normalize(this.vector)); + } +} diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/EmbeddingGenerationService.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/EmbeddingGenerationService.java new file mode 100644 index 00000000..86d4d27d --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/EmbeddingGenerationService.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.services.textembedding; + +import com.microsoft.semantickernel.builders.SemanticKernelBuilder; +import com.microsoft.semantickernel.services.AIService; +import reactor.core.publisher.Mono; + +import java.util.List; + +/** Interface for text embedding generation services */ +public interface EmbeddingGenerationService extends AIService { + /** + * Generates a list of embeddings associated to the data + * + * @param data List of texts to generate embeddings for + * @return List of embeddings of each data point + */ + Mono> generateEmbeddingsAsync(List data); +} diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/TextEmbeddingGenerationService.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/TextEmbeddingGenerationService.java new file mode 100644 index 00000000..28c2556a --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/TextEmbeddingGenerationService.java @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.services.textembedding; + +import com.microsoft.semantickernel.services.AIService; + +/** Interface for text embedding generation services */ +public interface TextEmbeddingGenerationService + extends EmbeddingGenerationService, AIService { +} diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/VectorOperations.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/VectorOperations.java new file mode 100644 index 00000000..ea0b77e0 --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/textembedding/VectorOperations.java @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.services.textembedding; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +final class VectorOperations { + + /** + * Calculates the cosine similarity of two vectors. The vectors must be equal in length and have + * non-zero norm. + * + * @param x First vector, which is not modified + * @param y Second vector, which is not modified + * @return The cosine similarity of the two vectors + */ + static float cosineSimilarity(@Nonnull List x, @Nonnull List y) { + Objects.requireNonNull(x); + Objects.requireNonNull(y); + + if (x.size() != y.size()) { + throw new IllegalArgumentException("Vectors lengths must be equal"); + } + + float dotProduct = dot(x, y); + float normX = dot(x, x); + float normY = dot(y, y); + + if (normX == 0 || normY == 0) { + throw new IllegalArgumentException("Vectors cannot have zero norm"); + } + + return dotProduct / (float) (Math.sqrt(normX) * Math.sqrt(normY)); + } + + /** + * Divides the elements of the vector by the divisor. + * + * @param vector Vector to divide, which is not modified + * @param divisor Divisor to apply to each element of the vector + * @return A new vector with the elements divided by the divisor + */ + static List divide(@Nonnull List vector, float divisor) { + Objects.requireNonNull(vector); + if (Float.isNaN(divisor)) { + throw new IllegalArgumentException("Divisor cannot be NaN"); + } + if (divisor == 0f) { + throw new IllegalArgumentException("Divisor cannot be zero"); + } + + return vector.stream().map(x -> x / divisor).collect(Collectors.toList()); + } + + static float dot(@Nonnull List x, @Nonnull List y) { + Objects.requireNonNull(x); + Objects.requireNonNull(y); + + if (x.size() != y.size()) { + throw new IllegalArgumentException("Vectors lengths must be equal"); + } + + float result = 0; + for (int i = 0; i < x.size(); ++i) { + result += x.get(i) * y.get(i); + } + + return result; + } + + /** + * Calculates the Euclidean length of a vector. + * + * @param vector Vector to calculate the length of, which is not modified + * @return The Euclidean length of the vector + */ + static float euclideanLength(@Nonnull List vector) { + Objects.requireNonNull(vector); + return (float) Math.sqrt(dot(vector, vector)); + } + + /** + * Multiplies the elements of the vector by the multiplier. + * + * @param vector Vector to multiply, which is not modified + * @param multiplier Multiplier to apply to each element of the vector + * @return A new vector with the elements multiplied by the multiplier + */ + static List multiply(@Nonnull List vector, float multiplier) { + Objects.requireNonNull(vector); + if (Float.isNaN(multiplier)) { + throw new IllegalArgumentException("Multiplier cannot be NaN"); + } + if (Float.isInfinite(multiplier)) { + throw new IllegalArgumentException("Multiplier cannot be infinite"); + } + + return vector.stream().map(x -> x * multiplier).collect(Collectors.toList()); + } + + /** + * Normalizes the vector such that the Euclidean length is 1. + * + * @param vector Vector to normalize, which is not modified + * @return A new, normalized vector + */ + static List normalize(@Nonnull List vector) { + Objects.requireNonNull(vector); + return divide(vector, euclideanLength(vector)); + } +} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplate.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplate.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplate.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplate.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/TemplateException.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/TemplateException.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/TemplateException.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/TemplateException.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/text/TextChunker.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/text/TextChunker.java similarity index 100% rename from java/semantickernel-api/src/main/java/com/microsoft/semantickernel/text/TextChunker.java rename to semantickernel-api/src/main/java/com/microsoft/semantickernel/text/TextChunker.java diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/contextvariables/converters/ContextVariableTypeConverterTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/contextvariables/converters/ContextVariableTypeConverterTest.java similarity index 82% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/contextvariables/converters/ContextVariableTypeConverterTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/contextvariables/converters/ContextVariableTypeConverterTest.java index 1b479753..2fdb2bfb 100644 --- a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/contextvariables/converters/ContextVariableTypeConverterTest.java +++ b/semantickernel-api/src/test/java/com/microsoft/semantickernel/contextvariables/converters/ContextVariableTypeConverterTest.java @@ -12,6 +12,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; public class ContextVariableTypeConverterTest { @@ -48,7 +49,27 @@ public Stream testConvertIntegerToString() { } })); + } + + class NoToString { + + } + + class WithToString { + + @Override + public String toString() { + return "foo"; + } + } + @Test + public void checkStringConversionIgnoresObjectReferences() { + Assertions.assertNull(StringVariableContextVariableTypeConverter.convertToString(null)); + Assertions.assertNull( + StringVariableContextVariableTypeConverter.convertToString(new NoToString())); + Assertions.assertEquals("foo", + StringVariableContextVariableTypeConverter.convertToString(new WithToString())); } private static class TestCase { diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/contextvariables/converters/NumberVariableContextVariableTypeConverterTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/contextvariables/converters/NumberVariableContextVariableTypeConverterTest.java similarity index 100% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/contextvariables/converters/NumberVariableContextVariableTypeConverterTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/contextvariables/converters/NumberVariableContextVariableTypeConverterTest.java diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/implementation/EmbeddedResourceLoaderTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/implementation/EmbeddedResourceLoaderTest.java similarity index 100% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/implementation/EmbeddedResourceLoaderTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/implementation/EmbeddedResourceLoaderTest.java diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettingsTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettingsTest.java similarity index 100% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettingsTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettingsTest.java diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/plugin/KernelPluginFactoryTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/plugin/KernelPluginFactoryTest.java similarity index 100% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/plugin/KernelPluginFactoryTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/plugin/KernelPluginFactoryTest.java diff --git a/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethodTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethodTest.java new file mode 100644 index 00000000..589705f3 --- /dev/null +++ b/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethodTest.java @@ -0,0 +1,422 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.semanticfunctions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter; +import com.microsoft.semantickernel.contextvariables.ContextVariableTypes; +import com.microsoft.semantickernel.orchestration.FunctionResult; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.plugin.KernelPlugin; +import com.microsoft.semantickernel.plugin.KernelPluginFactory; +import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; +import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import reactor.core.publisher.Mono; + +public class KernelFunctionFromMethodTest { + + public KernelFunctionFromMethodTest() { + } + + public static class ExamplePlugin { + + @DefineKernelFunction(name = "sqrt", description = "Take the square root of a number") + public static double sqrt( + @KernelFunctionParameter(name = "number1", description = "The number to take a square root of", type = double.class) double number1) { + return Math.sqrt(number1); + } + } + + @Test + void typeConversionOnMethodCall() { + KernelPlugin plugin = KernelPluginFactory.createFromObject( + new ExamplePlugin(), "ExamplePlugin"); + + Kernel kernel = Kernel.builder().build(); + + FunctionResult result = plugin + .get("sqrt") + .invokeAsync(kernel) + .withResultType(ContextVariableTypes.getGlobalVariableTypeForClass(String.class)) + .withArguments( + KernelFunctionArguments.builder() + .withVariable("number1", "12.0") + .build()) + .block(); + + Assertions.assertEquals("3.4641016151377544", result.getResult()); + } + + @Test + void testCreate() throws Exception { + Method method = String.class.getMethod("concat", String.class); + String pluginName = "test-plugin"; + String functionName = "concat"; + String description = "concatenate two strings"; + List parameters = Collections.singletonList(InputVariable.build( + "string1", String.class, "first string to concatenate", null, null, true)); + OutputVariable returnParameter = new OutputVariable<>( + "concatenated strings", String.class); + KernelFunction result = KernelFunctionFromMethod.create(method, "test", pluginName, + functionName, description, parameters, returnParameter); + assertEquals(functionName, result.getName()); + assertEquals(pluginName, result.getPluginName()); + assertEquals(description, result.getDescription()); + assertEquals(parameters, result.getMetadata().getParameters()); + assertEquals(returnParameter, result.getMetadata().getOutputVariableType()); + } + + /** + * Test of builder method, of class KernelFunctionFromMethod. + */ + @Test + void testBuilder() { + String pluginName = null; + String functionName = "concat"; + String description = "concatenate two strings"; + List parameters = Collections + .singletonList(new InputVariable("string1", + "java.lang.String", "first string to concatenate", null, true, null)); + OutputVariable returnParameter = new OutputVariable<>("java.lang.String", + "concatenated strings"); + KernelFunction result = new KernelFunctionFromPrompt.Builder() + .withName(functionName) + .withDescription(description) + .withInputParameters(parameters) + .withOutputVariable(returnParameter) + .withPromptTemplate((kernel, args, context) -> Mono.empty()) + .build(); + assertEquals(functionName, result.getName()); + assertEquals(pluginName, result.getPluginName()); + assertEquals(description, result.getDescription()); + // TODO: This assert fails because getParameters is a List>, not an List + // This feels like it's broken. Until this is fixed, we can compare the types + // assertEquals(parameters, result.getMetadata().getParameters()); + assertEquals(parameters.size(), result.getMetadata().getParameters().size()); + for (int i = 0; i < parameters.size(); i++) { + assertEquals(parameters.get(i).getDescription(), + result.getMetadata().getParameters().get(i).getDescription()); + assertEquals(parameters.get(i).getName(), + result.getMetadata().getParameters().get(i).getName()); + assertEquals(parameters.get(i).getType(), + result.getMetadata().getParameters().get(i).getType()); + assertEquals(parameters.get(i).isRequired(), + result.getMetadata().getParameters().get(i).isRequired()); + } + assertEquals(returnParameter, result.getMetadata().getOutputVariableType()); + assertEquals(returnParameter.getType(), + result.getMetadata().getOutputVariableType().getType()); + assertEquals(returnParameter.getDescription(), + result.getMetadata().getOutputVariableType().getDescription()); + } + + /** + * Test of invokeAsync method, of class KernelFunctionFromMethod. + */ + @Test + @Disabled("TODO: needs mocked http server") + void testInvokeAsync() { + } + + @TestFactory + public Stream runInvocationConversionTests() { + return Arrays.asList( + new NoAnnotation(), + new NoTypeOnAnnotation(), + new PrimitiveTypeOnAnnotation(), + new SuperClassTypeTypeOnAnnotation(), + new DefaultTypeOnAnnotation(), + new StringTargetTypeOnAnnotation(), + new ConvertUsingTargetType()) + .stream() + .map( + testClazz -> DynamicTest + .dynamicTest( + testClazz.getClass().getName() + "Test", + () -> { + + ContextVariableTypeConverter targetConverter = ContextVariableTypeConverter + .builder(TargetClass.class) + .fromObject(i -> { + if (i instanceof SourceClass) { + return new TargetClass(((SourceClass) i).value); + } + return (TargetClass) i; + }) + .toPromptString(i -> null) + .build(); + + Boolean result = (Boolean) KernelFunctionFromMethod.createFromMethod( + testClazz.getMethod(), + testClazz) + .build() + .invoke( + Kernel.builder().build(), + testClazz.getArguments(), + null, + InvocationContext.builder() + .withContextVariableConverter(targetConverter) + .build()) + .getResult(); + + Assertions.assertTrue(result); + + testClazz.assertCalled(); + })); + } + + interface InvocationTest { + + Method getMethod() throws NoSuchMethodException; + + KernelFunctionArguments getArguments(); + + void assertCalled(); + } + + @Nested + class NoAnnotation implements InvocationTest { + + boolean called = false; + + @DefineKernelFunction + public boolean method(Integer i) { + called = i == 123; + return called; + } + + public Method getMethod() throws NoSuchMethodException { + return this.getClass().getMethod("method", Integer.class); + } + + @Override + public KernelFunctionArguments getArguments() { + return KernelFunctionArguments.builder() + .withVariable("i", 123) + .build(); + } + + @Override + public void assertCalled() { + Assertions.assertTrue(called); + } + } + + @Nested + class NoTypeOnAnnotation implements InvocationTest { + + boolean called = false; + + @DefineKernelFunction + public boolean method( + @KernelFunctionParameter(name = "i") Integer i) { + called = i == 123; + return called; + } + + public Method getMethod() throws NoSuchMethodException { + return this.getClass().getMethod("method", Integer.class); + } + + @Override + public KernelFunctionArguments getArguments() { + return KernelFunctionArguments.builder() + .withVariable("i", 123) + .build(); + } + + @Override + public void assertCalled() { + Assertions.assertTrue(called); + } + } + + @Nested + class PrimitiveTypeOnAnnotation implements InvocationTest { + + boolean called = false; + + @DefineKernelFunction + public boolean method( + @KernelFunctionParameter(name = "i", type = int.class) int i) { + called = i == 123; + return called; + } + + public Method getMethod() throws NoSuchMethodException { + return this.getClass().getMethod("method", int.class); + } + + @Override + public KernelFunctionArguments getArguments() { + return KernelFunctionArguments.builder() + .withVariable("i", 123) + .build(); + } + + @Override + public void assertCalled() { + Assertions.assertTrue(called); + } + } + + @Nested + class SuperClassTypeTypeOnAnnotation implements InvocationTest { + + boolean called = false; + + @DefineKernelFunction + public boolean method( + @KernelFunctionParameter(name = "i", type = List.class) List i) { + called = i.size() == 3; + return called; + } + + public Method getMethod() throws NoSuchMethodException { + return this.getClass().getMethod("method", List.class); + } + + @Override + public KernelFunctionArguments getArguments() { + return KernelFunctionArguments.builder() + .withVariable("i", Arrays.asList(1, 2, 3)) + .build(); + } + + @Override + public void assertCalled() { + Assertions.assertTrue(called); + } + } + + @Nested + class DefaultTypeOnAnnotation implements InvocationTest { + + boolean called = false; + + @DefineKernelFunction + public boolean method( + @KernelFunctionParameter(name = "i", type = int.class, defaultValue = "123") int i) { + called = i == 123; + return called; + } + + public Method getMethod() throws NoSuchMethodException { + return this.getClass().getMethod("method", int.class); + } + + @Override + public KernelFunctionArguments getArguments() { + return KernelFunctionArguments.builder() + .build(); + } + + @Override + public void assertCalled() { + Assertions.assertTrue(called); + } + } + + @Nested + class StringTargetTypeOnAnnotation implements InvocationTest { + + boolean called = false; + + @DefineKernelFunction + public boolean method( + @KernelFunctionParameter(name = "i", type = String.class) String i) { + called = i.equals("123"); + return called; + } + + public Method getMethod() throws NoSuchMethodException { + return this.getClass().getMethod("method", String.class); + } + + @Override + public KernelFunctionArguments getArguments() { + + ContextVariableTypeConverter dbConverter = ContextVariableTypeConverter + .builder(BigDecimal.class) + .fromObject(i -> (BigDecimal) i) + .toPromptString(i -> null) + .build(); + + return KernelFunctionArguments.builder() + .withVariable("i", new BigDecimal(123), dbConverter) + .build(); + } + + @Override + public void assertCalled() { + Assertions.assertTrue(called); + } + } + + class TargetClass { + + final int value; + + TargetClass(int value) { + this.value = value; + } + } + + class SourceClass { + + final int value; + + SourceClass(int value) { + this.value = value; + } + } + + @Nested + class ConvertUsingTargetType implements InvocationTest { + + boolean called = false; + + @DefineKernelFunction + public boolean method( + @KernelFunctionParameter(name = "i", type = TargetClass.class) TargetClass i) { + called = i.value == 123; + return called; + } + + public Method getMethod() throws NoSuchMethodException { + return this.getClass().getMethod("method", TargetClass.class); + } + + @Override + public KernelFunctionArguments getArguments() { + + ContextVariableTypeConverter sourceConverter = ContextVariableTypeConverter + .builder(SourceClass.class) + .fromObject(i -> (SourceClass) i) + .toPromptString(i -> null) + .build(); + return KernelFunctionArguments.builder() + .withVariable("i", new SourceClass(123), sourceConverter) + .build(); + } + + @Override + public void assertCalled() { + Assertions.assertTrue(called); + } + } + +} \ No newline at end of file diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionYamlTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionYamlTest.java similarity index 100% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionYamlTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionYamlTest.java diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfigTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfigTest.java similarity index 100% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfigTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfigTest.java diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateFactoryTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateFactoryTest.java similarity index 100% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateFactoryTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateFactoryTest.java diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/services/AIServiceSelectorTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/services/AIServiceSelectorTest.java similarity index 100% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/services/AIServiceSelectorTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/services/AIServiceSelectorTest.java diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplateTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplateTest.java similarity index 90% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplateTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplateTest.java index e61933e4..eec1bfe7 100644 --- a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplateTest.java +++ b/semantickernel-api/src/test/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplateTest.java @@ -12,9 +12,8 @@ import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; -import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; -import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageTextContent; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -60,12 +59,12 @@ void testRenderAsync() { List history = Arrays.asList( new ChatHistory( Arrays.asList( - new ChatMessageContent(AuthorRole.SYSTEM, "a"), - new ChatMessageContent(AuthorRole.USER, "b"))), + ChatMessageTextContent.systemMessage("a"), + ChatMessageTextContent.userMessage("b"))), new ChatHistory( Arrays.asList( - new ChatMessageContent(AuthorRole.SYSTEM, "c"), - new ChatMessageContent(AuthorRole.USER, "d")))); + ChatMessageTextContent.systemMessage("c"), + ChatMessageTextContent.userMessage("d")))); KernelPlugin kernelPlugin = KernelPluginFactory.createFromObject( new StringFunctions(), diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/templateengine/semantickernel/CodeTokenizerTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/templateengine/semantickernel/CodeTokenizerTest.java similarity index 100% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/templateengine/semantickernel/CodeTokenizerTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/templateengine/semantickernel/CodeTokenizerTest.java diff --git a/java/semantickernel-api/src/test/java/com/microsoft/semantickernel/text/TextChunkerTest.java b/semantickernel-api/src/test/java/com/microsoft/semantickernel/text/TextChunkerTest.java similarity index 100% rename from java/semantickernel-api/src/test/java/com/microsoft/semantickernel/text/TextChunkerTest.java rename to semantickernel-api/src/test/java/com/microsoft/semantickernel/text/TextChunkerTest.java diff --git a/java/semantickernel-api/src/test/resources/Plugins/ExamplePlugins/ExampleFunction/config.json b/semantickernel-api/src/test/resources/Plugins/ExamplePlugins/ExampleFunction/config.json similarity index 100% rename from java/semantickernel-api/src/test/resources/Plugins/ExamplePlugins/ExampleFunction/config.json rename to semantickernel-api/src/test/resources/Plugins/ExamplePlugins/ExampleFunction/config.json diff --git a/java/semantickernel-api/src/test/resources/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt b/semantickernel-api/src/test/resources/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt similarity index 100% rename from java/semantickernel-api/src/test/resources/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt rename to semantickernel-api/src/test/resources/Plugins/ExamplePlugins/ExampleFunction/skprompt.txt diff --git a/java/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response1.txt b/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response1.txt similarity index 100% rename from java/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response1.txt rename to semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response1.txt diff --git a/java/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response2.txt b/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response2.txt similarity index 100% rename from java/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response2.txt rename to semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response2.txt diff --git a/java/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response3.txt b/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response3.txt similarity index 100% rename from java/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response3.txt rename to semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response3.txt diff --git a/java/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response4.txt b/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response4.txt similarity index 100% rename from java/semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response4.txt rename to semantickernel-api/src/test/resources/com/microsoft/semantickernel/planner/stepwiseplanner/response4.txt diff --git a/semantickernel-api/src/test/resources/log4j2.xml b/semantickernel-api/src/test/resources/log4j2.xml new file mode 100644 index 00000000..50a638f0 --- /dev/null +++ b/semantickernel-api/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/semantickernel-api/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/semantickernel-api/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker similarity index 100% rename from java/semantickernel-api/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker rename to semantickernel-api/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/java/semantickernel-bom/pom.xml b/semantickernel-bom/pom.xml similarity index 71% rename from java/semantickernel-bom/pom.xml rename to semantickernel-bom/pom.xml index 41a922b1..b7f52070 100644 --- a/java/semantickernel-bom/pom.xml +++ b/semantickernel-bom/pom.xml @@ -5,7 +5,7 @@ com.microsoft.semantic-kernel semantickernel-bom - 1.1.5 + 1.2.0 pom Semantic Kernel Java BOM @@ -13,7 +13,7 @@ https://www.github.com/microsoft/semantic-kernel - 2.16.1 + 2.17.1 @@ -43,11 +43,23 @@ + + io.opentelemetry + opentelemetry-bom + 1.39.0 + pom + import + com.microsoft.semantic-kernel semantickernel-api ${project.version} + + com.microsoft.semantic-kernel + semantickernel-experimental + ${project.version} + com.microsoft.semantic-kernel semantickernel-gpt3-tokenizer @@ -65,19 +77,20 @@ com.microsoft.semantic-kernel - aiservices-huggingface + semantickernel-aiservices-huggingface ${project.version} com.microsoft.semantic-kernel - aiservices-ollama + semantickernel-aiservices-openai ${project.version} com.microsoft.semantic-kernel - semantickernel-aiservices-openai + semantickernel-aiservices-google ${project.version} + com.azure azure-ai-openai @@ -86,8 +99,30 @@ com.azure azure-identity - 1.12.1 + 1.12.2 + + + com.azure + azure-core + 1.49.1 + + com.azure + azure-search-documents + 11.7.0-beta.3 + + + com.azure + azure-core-serializer-json-jackson + + + + + redis.clients + jedis + 5.1.0 + + com.fasterxml.jackson.core jackson-annotations @@ -151,6 +186,12 @@ commons-text 1.11.0 + + + com.google.cloud + google-cloud-vertexai + 1.1.0 + @@ -167,6 +208,33 @@ + + github-packages + + false + + + + github + GitHub Packages + https://maven.pkg.github.com/microsoft/semantic-kernel-java + + + + + central + https://repo1.maven.org/maven2 + + + github + GitHub Packages + https://maven.pkg.github.com/microsoft/semantic-kernel-java + + true + + + + @@ -188,6 +256,6 @@ https://github.com/microsoft/semantic-kernel scm:git:https://github.com/microsoft/semantic-kernel.git scm:git:https://github.com/microsoft/semantic-kernel.git - java-1.1.5 + java-1.2.0 diff --git a/semantickernel-experimental/pom.xml b/semantickernel-experimental/pom.xml new file mode 100644 index 00000000..327ff1e6 --- /dev/null +++ b/semantickernel-experimental/pom.xml @@ -0,0 +1,129 @@ + + + 4.0.0 + + com.microsoft.semantic-kernel + semantickernel-parent + 1.2.0 + + + semantickernel-experimental + jar + Semantic Kernel Experimental API + Defines the public interface for the Semantic Kernel experimental features + + + com.azure + azure-ai-openai + + + org.slf4j + slf4j-api + + + com.fasterxml.jackson.core + jackson-databind + compile + + + com.fasterxml.jackson.core + jackson-core + compile + + + com.github.jknack + handlebars + + + com.google.code.findbugs + jsr305 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + compile + + + com.github.spotbugs + spotbugs-annotations + + + org.apache.commons + commons-text + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.wiremock + wiremock + 3.3.1 + test + + + + javax.xml.stream + stax-api + provided + + + + org.mockito + mockito-junit-jupiter + 5.11.0 + test + + + io.opentelemetry + opentelemetry-api + + + + + com.microsoft.semantic-kernel + semantickernel-api + + + + + redis.clients + jedis + + + + + com.azure + azure-search-documents + + + com.azure + azure-core-serializer-json-jackson + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + false + 1 + + + + + + \ No newline at end of file diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStore.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStore.java new file mode 100644 index 00000000..39d13f75 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStore.java @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.azureaisearch; + +import com.azure.search.documents.indexes.SearchIndexAsyncClient; +import com.azure.search.documents.indexes.models.SearchIndex; +import com.microsoft.semantickernel.data.VectorStore; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +public class AzureAISearchVectorStore + implements VectorStore> { + + private final SearchIndexAsyncClient client; + private final AzureAISearchVectorStoreOptions options; + + /** + * Creates a new instance of {@link AzureAISearchVectorStore}. + * + * @param client The Azure AI Search client. + * @param options The options for the vector store. + */ + @SuppressFBWarnings("EI_EXPOSE_REP2") + public AzureAISearchVectorStore(@Nonnull SearchIndexAsyncClient client, + @Nonnull AzureAISearchVectorStoreOptions options) { + this.client = client; + this.options = options; + } + + /** + * Gets a new instance of {@link AzureAISearchVectorStoreRecordCollection} + * + * @param collectionName The name of the collection. + * @param recordClass The class type of the record. + * @param recordDefinition The record definition. + * @return The collection. + */ + @Override + public AzureAISearchVectorStoreRecordCollection getCollection( + @Nonnull String collectionName, + @Nonnull Class recordClass, + VectorStoreRecordDefinition recordDefinition) { + + if (options.getVectorStoreRecordCollectionFactory() != null) { + return options.getVectorStoreRecordCollectionFactory() + .createVectorStoreRecordCollection( + client, + collectionName, + AzureAISearchVectorStoreRecordCollectionOptions.builder() + .withRecordClass(recordClass) + .withRecordDefinition(recordDefinition) + .build()); + } + + return new AzureAISearchVectorStoreRecordCollection<>(client, collectionName, + AzureAISearchVectorStoreRecordCollectionOptions.builder() + .withRecordClass(recordClass) + .withRecordDefinition(recordDefinition) + .build()); + } + + /** + * Gets the names of all collections in the Azure AI Search vector store. + * + * @return A list of collection names. + */ + @Override + public Mono> getCollectionNamesAsync() { + return client.listIndexes().map(SearchIndex::getName).collectList(); + } + + /** + * Creates a new {@link Builder} instance. + * + * @return The new builder instance. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link AzureAISearchVectorStore}. + */ + public static class Builder { + @Nullable + private SearchIndexAsyncClient client; + @Nullable + private AzureAISearchVectorStoreOptions options; + + /** + * Sets the Azure AI Search client. + * + * @param client The Azure AI Search client. + * @return The updated builder instance. + */ + @SuppressFBWarnings("EI_EXPOSE_REP2") + public Builder withClient(@Nonnull SearchIndexAsyncClient client) { + this.client = client; + return this; + } + + /** + * Sets the options for the Azure AI Search vector store. + * + * @param options The options for the Azure AI Search vector store. + * @return The updated builder instance. + */ + public Builder withOptions(@Nonnull AzureAISearchVectorStoreOptions options) { + this.options = options; + return this; + } + + /** + * Builds the Azure AI Search vector store. + * + * @return The Azure AI Search vector store. + */ + public AzureAISearchVectorStore build() { + if (client == null) { + throw new IllegalStateException("client is required"); + } + if (options == null) { + throw new IllegalStateException("options is required"); + } + + return new AzureAISearchVectorStore(client, options); + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreCollectionCreateMapping.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreCollectionCreateMapping.java new file mode 100644 index 00000000..e077a510 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreCollectionCreateMapping.java @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.azureaisearch; + +import com.azure.search.documents.indexes.models.ExhaustiveKnnAlgorithmConfiguration; +import com.azure.search.documents.indexes.models.ExhaustiveKnnParameters; +import com.azure.search.documents.indexes.models.HnswAlgorithmConfiguration; +import com.azure.search.documents.indexes.models.HnswParameters; +import com.azure.search.documents.indexes.models.SearchField; +import com.azure.search.documents.indexes.models.SearchFieldDataType; +import com.azure.search.documents.indexes.models.VectorSearchAlgorithmConfiguration; +import com.azure.search.documents.indexes.models.VectorSearchAlgorithmMetric; +import com.azure.search.documents.indexes.models.VectorSearchProfile; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDataField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordKeyField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordVectorField; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Objects; + +public class AzureAISearchVectorStoreCollectionCreateMapping { + + private static String getVectorSearchProfileName(VectorStoreRecordVectorField vectorField) { + return vectorField.getName() + "Profile"; + } + + private static String getAlgorithmConfigName(VectorStoreRecordVectorField vectorField) { + return vectorField.getName() + "AlgorithmConfig"; + } + + private static VectorSearchAlgorithmMetric getAlgorithmMetric( + @Nonnull VectorStoreRecordVectorField vectorField) { + if (vectorField.getDistanceFunction() == null) { + return VectorSearchAlgorithmMetric.COSINE; + } + + switch (vectorField.getDistanceFunction()) { + case COSINE_SIMILARITY: + return VectorSearchAlgorithmMetric.COSINE; + case DOT_PRODUCT: + return VectorSearchAlgorithmMetric.DOT_PRODUCT; + case EUCLIDEAN: + return VectorSearchAlgorithmMetric.EUCLIDEAN; + default: + throw new IllegalArgumentException( + "Unsupported distance function: " + vectorField.getDistanceFunction()); + } + } + + private static VectorSearchAlgorithmConfiguration getAlgorithmConfig( + @Nonnull VectorStoreRecordVectorField vectorField) { + if (vectorField.getIndexKind() == null) { + return new HnswAlgorithmConfiguration(getAlgorithmConfigName(vectorField)) + .setParameters(new HnswParameters().setMetric(getAlgorithmMetric(vectorField))); + } + + switch (vectorField.getIndexKind()) { + case HNSW: + return new HnswAlgorithmConfiguration(getAlgorithmConfigName(vectorField)) + .setParameters(new HnswParameters().setMetric(getAlgorithmMetric(vectorField))); + case FLAT: + return new ExhaustiveKnnAlgorithmConfiguration(getAlgorithmConfigName(vectorField)) + .setParameters( + new ExhaustiveKnnParameters().setMetric(getAlgorithmMetric(vectorField))); + default: + throw new IllegalArgumentException( + "Unsupported index kind: " + vectorField.getIndexKind()); + } + } + + public static SearchField mapKeyField(VectorStoreRecordKeyField keyField) { + return new SearchField(keyField.getName(), SearchFieldDataType.STRING) + .setKey(true) + .setFilterable(true); + } + + public static SearchField mapDataField(VectorStoreRecordDataField dataField) { + if (dataField.getFieldType() == null) { + throw new IllegalArgumentException( + "Field type is required: " + dataField.getName()); + } + + return new SearchField(dataField.getName(), + getSearchFieldDataType(dataField.getFieldType())) + .setFilterable(dataField.isFilterable()); + } + + public static SearchField mapVectorField(VectorStoreRecordVectorField vectorField) { + return new SearchField(vectorField.getName(), + SearchFieldDataType.collection(SearchFieldDataType.SINGLE)) + .setSearchable(true) + .setVectorSearchDimensions(vectorField.getDimensions()) + .setVectorSearchProfileName(getVectorSearchProfileName(vectorField)); + } + + public static void updateVectorSearchParameters( + List algorithms, + List profiles, + VectorStoreRecordVectorField vectorField) { + if (vectorField.getDimensions() <= 0) { + throw new IllegalArgumentException("Vector field dimensions must be greater than 0"); + } + + algorithms.add(getAlgorithmConfig(vectorField)); + profiles.add(new VectorSearchProfile(getVectorSearchProfileName(vectorField), + getAlgorithmConfigName(vectorField))); + } + + public static SearchFieldDataType getSearchFieldDataType(Class fieldType) { + if (fieldType == String.class) { + return SearchFieldDataType.STRING; + } else if (fieldType == Integer.class || fieldType == int.class) { + return SearchFieldDataType.INT32; + } else if (fieldType == Long.class || fieldType == long.class) { + return SearchFieldDataType.INT64; + } else if (fieldType == Float.class || fieldType == float.class) { + return SearchFieldDataType.DOUBLE; + } else if (fieldType == Double.class || fieldType == double.class) { + return SearchFieldDataType.DOUBLE; + } else if (fieldType == Boolean.class || fieldType == boolean.class) { + return SearchFieldDataType.BOOLEAN; + } else if (fieldType == OffsetDateTime.class) { + return SearchFieldDataType.DATE_TIME_OFFSET; + } else { + throw new IllegalArgumentException("Unsupported field type: " + fieldType.getName()); + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreOptions.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreOptions.java new file mode 100644 index 00000000..d7bb0314 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreOptions.java @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.azureaisearch; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class AzureAISearchVectorStoreOptions { + @Nullable + private final AzureAISearchVectorStoreRecordCollectionFactory vectorStoreRecordCollectionFactory; + + /** + * Creates a new instance of the Azure AI Search vector store options. + * + * @param vectorStoreRecordCollectionFactory The vector store record collection factory. + */ + public AzureAISearchVectorStoreOptions( + @Nullable AzureAISearchVectorStoreRecordCollectionFactory vectorStoreRecordCollectionFactory) { + this.vectorStoreRecordCollectionFactory = vectorStoreRecordCollectionFactory; + } + + /** + * Creates a new instance of the Azure AI Search vector store options. + */ + public AzureAISearchVectorStoreOptions() { + this(null); + } + + /** + * Creates a new builder. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the vector store record collection factory. + * + * @return the vector store record collection factory + */ + @Nullable + public AzureAISearchVectorStoreRecordCollectionFactory getVectorStoreRecordCollectionFactory() { + return vectorStoreRecordCollectionFactory; + } + + /** + * Builder for Azure AI Search vector store options. + * + */ + public static class Builder { + @Nullable + private AzureAISearchVectorStoreRecordCollectionFactory vectorStoreRecordCollectionFactory; + + /** + * Sets the vector store record collection factory. + * + * @param vectorStoreRecordCollectionFactory The vector store record collection factory. + * @return The updated builder instance. + */ + public Builder withVectorStoreRecordCollectionFactory( + AzureAISearchVectorStoreRecordCollectionFactory vectorStoreRecordCollectionFactory) { + this.vectorStoreRecordCollectionFactory = vectorStoreRecordCollectionFactory; + return this; + } + + /** + * Builds the Azure AI Search vector store options. + * + * @return The Azure AI Search vector store options. + */ + public AzureAISearchVectorStoreOptions build() { + return new AzureAISearchVectorStoreOptions(vectorStoreRecordCollectionFactory); + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreRecordCollection.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreRecordCollection.java new file mode 100644 index 00000000..9576b122 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreRecordCollection.java @@ -0,0 +1,271 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.azureaisearch; + +import com.azure.search.documents.SearchAsyncClient; +import com.azure.search.documents.SearchDocument; +import com.azure.search.documents.indexes.SearchIndexAsyncClient; +import com.azure.search.documents.indexes.models.SearchField; +import com.azure.search.documents.indexes.models.SearchIndex; +import com.azure.search.documents.indexes.models.VectorSearch; +import com.azure.search.documents.indexes.models.VectorSearchAlgorithmConfiguration; +import com.azure.search.documents.indexes.models.VectorSearchProfile; +import com.azure.search.documents.models.IndexDocumentsResult; +import com.azure.search.documents.models.IndexingResult; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordKeyField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordVectorField; +import com.microsoft.semantickernel.exceptions.SKException; +import com.microsoft.semantickernel.data.VectorStoreRecordCollection; +import com.microsoft.semantickernel.data.VectorStoreRecordMapper; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDataField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; +import com.microsoft.semantickernel.data.recordoptions.DeleteRecordOptions; +import com.microsoft.semantickernel.data.recordoptions.GetRecordOptions; +import com.microsoft.semantickernel.data.recordoptions.UpsertRecordOptions; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +public class AzureAISearchVectorStoreRecordCollection + implements VectorStoreRecordCollection { + + private static final HashSet> supportedKeyTypes = new HashSet<>( + Collections.singletonList( + String.class)); + + private static final HashSet> supportedDataTypes = new HashSet<>( + Arrays.asList( + String.class, + Integer.class, + int.class, + Long.class, + long.class, + Float.class, + float.class, + Double.class, + double.class, + Boolean.class, + boolean.class, + OffsetDateTime.class)); + + private static final HashSet> supportedVectorTypes = new HashSet<>( + Arrays.asList( + List.class, + Collection.class)); + + private final SearchIndexAsyncClient client; + private final String collectionName; + private final Map clientsByIndex = new ConcurrentHashMap<>(); + private final AzureAISearchVectorStoreRecordCollectionOptions options; + private final VectorStoreRecordDefinition recordDefinition; + + // List of non-vector fields. Used to fetch only non-vector fields when vectors are not requested + private final List nonVectorFields = new ArrayList<>(); + + @SuppressFBWarnings("EI_EXPOSE_REP2") + public AzureAISearchVectorStoreRecordCollection( + @Nonnull SearchIndexAsyncClient client, + @Nonnull String collectionName, + @Nonnull AzureAISearchVectorStoreRecordCollectionOptions options) { + this.client = client; + this.collectionName = collectionName; + this.options = options; + + // If record definition is not provided, create one from the record class + this.recordDefinition = options.getRecordDefinition() == null + ? VectorStoreRecordDefinition.fromRecordClass(options.getRecordClass()) + : options.getRecordDefinition(); + + // Validate supported types + VectorStoreRecordDefinition.validateSupportedKeyTypes(this.options.getRecordClass(), + this.recordDefinition, supportedKeyTypes); + VectorStoreRecordDefinition.validateSupportedDataTypes(this.options.getRecordClass(), + this.recordDefinition, supportedDataTypes); + VectorStoreRecordDefinition.validateSupportedVectorTypes(this.options.getRecordClass(), + this.recordDefinition, supportedVectorTypes); + + // Add non-vector fields to the list + nonVectorFields.add(this.recordDefinition.getKeyField().getName()); + nonVectorFields.addAll(this.recordDefinition.getDataFields().stream() + .map(VectorStoreRecordDataField::getName) + .collect(Collectors.toList())); + } + + @Override + public String getCollectionName() { + return collectionName; + } + + private Mono> getIndexesAsync() { + return client.listIndexes().map(SearchIndex::getName).collect(Collectors.toList()); + } + + @Override + public Mono collectionExistsAsync() { + return getIndexesAsync() + .map(list -> list.stream().anyMatch(name -> name.equalsIgnoreCase(collectionName))); + } + + @Override + public Mono createCollectionAsync() { + List searchFields = new ArrayList<>(); + List algorithms = new ArrayList<>(); + List profiles = new ArrayList<>(); + + for (VectorStoreRecordField field : this.recordDefinition.getAllFields()) { + if (field instanceof VectorStoreRecordKeyField) { + searchFields.add(AzureAISearchVectorStoreCollectionCreateMapping + .mapKeyField((VectorStoreRecordKeyField) field)); + } else if (field instanceof VectorStoreRecordDataField) { + searchFields.add(AzureAISearchVectorStoreCollectionCreateMapping + .mapDataField((VectorStoreRecordDataField) field)); + } else { + searchFields.add(AzureAISearchVectorStoreCollectionCreateMapping + .mapVectorField((VectorStoreRecordVectorField) field)); + AzureAISearchVectorStoreCollectionCreateMapping + .updateVectorSearchParameters(algorithms, profiles, + (VectorStoreRecordVectorField) field); + } + } + + SearchIndex newIndex = new SearchIndex(collectionName) + .setFields(searchFields) + .setVectorSearch(new VectorSearch() + .setAlgorithms(algorithms) + .setProfiles(profiles)); + + return client.createIndex(newIndex).then(); + } + + @Override + public Mono createCollectionIfNotExistsAsync() { + return collectionExistsAsync().flatMap( + exists -> { + if (!exists) { + return createCollectionAsync(); + } + return Mono.empty(); + }); + } + + @Override + public Mono deleteCollectionAsync() { + return client.deleteIndex(this.collectionName).then(); + } + + @Override + public Mono getAsync( + @Nonnull String key, GetRecordOptions options) { + SearchAsyncClient client = this.getSearchClient(this.collectionName); + + // If vectors are not requested, only fetch non-vector fields + List selectedFields = null; + if (options != null && !options.includeVectors()) { + selectedFields = Collections.unmodifiableList(nonVectorFields); + } + + VectorStoreRecordMapper mapper = this.options + .getVectorStoreRecordMapper(); + + // Use custom mapper if available + if (mapper != null && mapper.getStorageModelToRecordMapper() != null) { + return client.getDocument(key, SearchDocument.class) + .map(this.options.getVectorStoreRecordMapper()::mapStorageModeltoRecord); + } + + return client.getDocumentWithResponse(key, this.options.getRecordClass(), selectedFields) + .map(response -> { + if (response.getStatusCode() == 404) { + throw new SKException("Record not found: " + key); + } + return response.getValue(); + }); + + } + + @Override + public Mono> getBatchAsync( + @Nonnull List keys, + GetRecordOptions options) { + return Flux.fromIterable(keys) + .flatMap(key -> getAsync(key, options).flux()) + .collect(Collectors.toList()); + } + + @Override + public Mono upsertAsync(@Nonnull Record record, UpsertRecordOptions options) { + return upsertBatchAsync(Collections.singletonList(record), options) + .map(Collection::iterator) + .map(Iterator::next); + } + + @Override + public Mono> upsertBatchAsync( + @Nonnull List records, UpsertRecordOptions options) { + if (records.isEmpty()) { + return Mono.just(Collections.emptyList()); + } + SearchAsyncClient client = this.getSearchClient(this.collectionName); + + VectorStoreRecordMapper mapper = this.options + .getVectorStoreRecordMapper(); + Iterable documents; + + // Use custom mapper if available + if (mapper != null && mapper.getRecordToStorageModelMapper() != null) { + documents = records.stream() + .map(this.options.getVectorStoreRecordMapper()::mapRecordToStorageModel) + .collect(Collectors.toList()); + } else { + documents = records; + } + + return client.uploadDocuments(documents) + .map(IndexDocumentsResult::getResults) + .map( + results -> results.stream() + .map(IndexingResult::getKey) + .collect(Collectors.toList())); + } + + @Override + public Mono deleteAsync(String key, DeleteRecordOptions options) { + return deleteBatchAsync(Collections.singletonList(key), options); + } + + @Override + public Mono deleteBatchAsync(List keys, DeleteRecordOptions options) { + SearchAsyncClient client = this.getSearchClient(this.collectionName); + + return client.deleteDocuments(keys.stream().map(key -> { + SearchDocument document = new SearchDocument(); + document.put(this.recordDefinition.getKeyField().getName(), key); + return document; + }).collect(Collectors.toList())).then(); + } + + /** + * Get a search client for the index specified. Note: the index might not exist, but we avoid + * checking everytime and the extra latency. + * + * @param indexName Index name + * @return Search client ready to read/write + */ + protected SearchAsyncClient getSearchClient(@Nonnull String indexName) { + return clientsByIndex.computeIfAbsent( + indexName, client::getSearchAsyncClient); + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreRecordCollectionFactory.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreRecordCollectionFactory.java new file mode 100644 index 00000000..c5041284 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreRecordCollectionFactory.java @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.azureaisearch; + +import com.azure.search.documents.indexes.SearchIndexAsyncClient; + +/** + * Factory for creating Azure AI Search vector store record collections. + * + */ +public interface AzureAISearchVectorStoreRecordCollectionFactory { + + /** + * Creates a new Azure AI Search vector store record collection. + * + * @param client The Azure AI Search client. + * @param collectionName The name of the collection. + * @param options The options for the collection. + * @return The new Azure AI Search vector store record collection. + */ + AzureAISearchVectorStoreRecordCollection createVectorStoreRecordCollection( + SearchIndexAsyncClient client, + String collectionName, + AzureAISearchVectorStoreRecordCollectionOptions options); +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreRecordCollectionOptions.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreRecordCollectionOptions.java new file mode 100644 index 00000000..45fb410c --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/azureaisearch/AzureAISearchVectorStoreRecordCollectionOptions.java @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.azureaisearch; + +import com.azure.search.documents.SearchDocument; +import com.microsoft.semantickernel.data.VectorStoreRecordMapper; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Options for an Azure AI Search vector store. + * + * @param the record type + */ +public class AzureAISearchVectorStoreRecordCollectionOptions { + private final Class recordClass; + @Nullable + private final VectorStoreRecordMapper vectorStoreRecordMapper; + + @Nullable + private final VectorStoreRecordDefinition recordDefinition; + + /** + * Creates a new builder. + * + * @param the record type + * @return the builder + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Gets the record class. + * + * @return the record class + */ + public Class getRecordClass() { + return recordClass; + } + + /** + * Gets the record definition. + * + * @return the record definition + */ + @Nullable + public VectorStoreRecordDefinition getRecordDefinition() { + return recordDefinition; + } + + /** + * Gets the vector store record mapper. + * + * @return the vector store record mapper + */ + @Nullable + public VectorStoreRecordMapper getVectorStoreRecordMapper() { + return vectorStoreRecordMapper; + } + + private AzureAISearchVectorStoreRecordCollectionOptions( + @Nonnull Class recordClass, + @Nullable VectorStoreRecordMapper vectorStoreRecordMapper, + @Nullable VectorStoreRecordDefinition recordDefinition) { + this.recordClass = recordClass; + this.vectorStoreRecordMapper = vectorStoreRecordMapper; + this.recordDefinition = recordDefinition; + } + + /** + * Builder for {@link AzureAISearchVectorStoreRecordCollectionOptions}. + * + * @param the record type + */ + public static class Builder { + @Nullable + private VectorStoreRecordMapper vectorStoreRecordMapper; + @Nullable + private Class recordClass; + @Nullable + private VectorStoreRecordDefinition recordDefinition; + + public Builder withRecordClass(Class recordClass) { + this.recordClass = recordClass; + return this; + } + + /** + * Sets the vector store record mapper. + * + * @param vectorStoreRecordMapper the vector store record mapper + * @return the builder + */ + public Builder withVectorStoreRecordMapper( + VectorStoreRecordMapper vectorStoreRecordMapper) { + this.vectorStoreRecordMapper = vectorStoreRecordMapper; + return this; + } + + /** + * Sets the record definition. + * + * @param recordDefinition the record definition + * @return the builder + */ + public Builder withRecordDefinition(VectorStoreRecordDefinition recordDefinition) { + this.recordDefinition = recordDefinition; + return this; + } + + /** + * Builds the options. + * + * @return the options + */ + public AzureAISearchVectorStoreRecordCollectionOptions build() { + if (recordClass == null) { + throw new IllegalArgumentException("recordClass must be provided"); + } + + return new AzureAISearchVectorStoreRecordCollectionOptions<>( + recordClass, + vectorStoreRecordMapper, + recordDefinition); + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStore.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStore.java new file mode 100644 index 00000000..7e561e43 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStore.java @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.redis; + +import com.microsoft.semantickernel.builders.SemanticKernelBuilder; +import com.microsoft.semantickernel.data.VectorStore; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import reactor.core.publisher.Mono; +import redis.clients.jedis.JedisPooled; + +public class RedisVectorStore implements VectorStore> { + + private final JedisPooled client; + private final RedisVectorStoreOptions options; + + /** + * Creates a new instance of the Redis vector store. + * + * @param client The Redis client. + * @param options The options for the vector store. + */ + @SuppressFBWarnings("EI_EXPOSE_REP2") + public RedisVectorStore(@Nonnull JedisPooled client, + @Nonnull RedisVectorStoreOptions options) { + this.client = client; + this.options = options; + } + + /** + * Gets a collection from the vector store. + * + * @param collectionName The name of the collection. + * @param recordClass The class type of the record. + * @param recordDefinition The record definition. + * @return The collection. + */ + @Override + public RedisVectorStoreRecordCollection getCollection( + @Nonnull String collectionName, + @Nonnull Class recordClass, + @Nullable VectorStoreRecordDefinition recordDefinition) { + + if (options.getVectorStoreRecordCollectionFactory() != null) { + return options.getVectorStoreRecordCollectionFactory() + .createVectorStoreRecordCollection( + client, + collectionName, + RedisVectorStoreRecordCollectionOptions.builder() + .withRecordClass(recordClass) + .withRecordDefinition(recordDefinition) + .build()); + } + + return new RedisVectorStoreRecordCollection<>(client, collectionName, + RedisVectorStoreRecordCollectionOptions.builder() + .withRecordClass(recordClass) + .withRecordDefinition(recordDefinition) + .build()); + } + + /** + * Gets the names of all collections in the vector store. + * + * @return A list of collection names. + */ + @Override + public Mono> getCollectionNamesAsync() { + return Mono.fromCallable(() -> new ArrayList<>(client.ftList())); + } + + /** + * Builder for the Redis vector store. + * + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder implements SemanticKernelBuilder { + + @Nullable + private JedisPooled client; + @Nullable + private RedisVectorStoreOptions options; + + /** + * Sets the Redis client. + * + * @param client the Redis client + * @return the builder + */ + @SuppressFBWarnings("EI_EXPOSE_REP2") + public Builder withClient(JedisPooled client) { + this.client = client; + return this; + } + + /** + * Sets the options for the vector store. + * + * @param options the options for the vector store + * @return the builder + */ + public Builder withOptions(RedisVectorStoreOptions options) { + this.options = options; + return this; + } + + @Override + public RedisVectorStore build() { + if (client == null) { + throw new IllegalArgumentException("client is required"); + } + + if (options == null) { + throw new IllegalArgumentException("options is required"); + } + + return new RedisVectorStore(client, options); + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreCollectionCreateMapping.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreCollectionCreateMapping.java new file mode 100644 index 00000000..fe044c73 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreCollectionCreateMapping.java @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.redis; + +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDataField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordKeyField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordVectorField; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import redis.clients.jedis.search.Schema; + +public class RedisVectorStoreCollectionCreateMapping { + private static final HashSet> supportedFilterableNumericTypes = new HashSet<>( + Arrays.asList( + Integer.class, + int.class, + Double.class, + double.class, + Long.class, + long.class, + Float.class, + float.class, + Short.class, + short.class, + Byte.class, + byte.class)); + + private static String getAlgorithmMetric( + VectorStoreRecordVectorField vectorField) { + if (vectorField.getDistanceFunction() == null) { + return RedisVectorDistanceMetric.COSINE; + } + + switch (vectorField.getDistanceFunction()) { + case COSINE_SIMILARITY: + return RedisVectorDistanceMetric.COSINE; + case DOT_PRODUCT: + return RedisVectorDistanceMetric.DOT_PRODUCT; + case EUCLIDEAN: + return RedisVectorDistanceMetric.EUCLIDEAN; + default: + throw new IllegalArgumentException( + "Unsupported distance function: " + vectorField.getDistanceFunction()); + } + } + + private static Schema.VectorField.VectorAlgo getAlgorithmConfig( + VectorStoreRecordVectorField vectorField) { + if (vectorField.getIndexKind() == null) { + return Schema.VectorField.VectorAlgo.HNSW; + } + + switch (vectorField.getIndexKind()) { + case HNSW: + return Schema.VectorField.VectorAlgo.HNSW; + case FLAT: + return Schema.VectorField.VectorAlgo.FLAT; + default: + throw new IllegalArgumentException( + "Unsupported index kind: " + vectorField.getIndexKind()); + } + } + + private static String getRedisPath(String name) { + return "$." + name; + } + + public static Schema mapToSchema(List fields) { + Schema schema = new Schema(); + + for (VectorStoreRecordField field : fields) { + if (field instanceof VectorStoreRecordKeyField) { + continue; + } + + if (field instanceof VectorStoreRecordDataField + && ((VectorStoreRecordDataField) field).isFilterable()) { + VectorStoreRecordDataField dataField = (VectorStoreRecordDataField) field; + + if (dataField.getFieldType() == null) { + throw new IllegalArgumentException( + "Field type is required for filterable fields: " + dataField.getName()); + } + + if (dataField.getFieldType().equals(String.class)) { + schema.addTextField(getRedisPath(dataField.getName()), 1.0); + } else if (supportedFilterableNumericTypes.contains(dataField.getFieldType())) { + schema.addNumericField(getRedisPath(dataField.getName())); + } else { + throw new IllegalArgumentException( + "Unsupported field type for numeric filterable fields: " + + dataField.getName()); + } + + } + + if (field instanceof VectorStoreRecordVectorField) { + VectorStoreRecordVectorField vectorField = (VectorStoreRecordVectorField) field; + + if (vectorField.getDimensions() < 1) { + throw new IllegalArgumentException( + "Dimensions must be greater than 0 for vector fields: " + + vectorField.getName()); + } + + Schema.VectorField.VectorAlgo algorithm = getAlgorithmConfig(vectorField); + String metric = getAlgorithmMetric(vectorField); + + Map attributes = new HashMap<>(); + attributes.put(RedisIndexSchemaParams.TYPE, "FLOAT32"); + attributes.put(RedisIndexSchemaParams.DIMENSIONS, vectorField.getDimensions()); + attributes.put(RedisIndexSchemaParams.DISTANCE_METRIC, metric); + + schema.addVectorField(getRedisPath(vectorField.getName()), algorithm, attributes); + } + } + + return schema; + } + + static class RedisIndexSchemaParams { + public static final String TYPE = "TYPE"; + public static final String DIMENSIONS = "DIM"; + public static final String DISTANCE_METRIC = "DISTANCE_METRIC"; + } + + static class RedisVectorDistanceMetric { + public static final String EUCLIDEAN = "L2"; + public static final String DOT_PRODUCT = "IP"; + public static final String COSINE = "COSINE"; + } + +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreOptions.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreOptions.java new file mode 100644 index 00000000..9b70fb49 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreOptions.java @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.redis; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class RedisVectorStoreOptions { + @Nullable + private final RedisVectorStoreRecordCollectionFactory vectorStoreRecordCollectionFactory; + + /** + * Creates a new instance of the Redis vector store options. + * + * @param vectorStoreRecordCollectionFactory The vector store record collection factory. + */ + public RedisVectorStoreOptions( + @Nullable RedisVectorStoreRecordCollectionFactory vectorStoreRecordCollectionFactory) { + this.vectorStoreRecordCollectionFactory = vectorStoreRecordCollectionFactory; + } + + /** + * Creates a new instance of the Redis vector store options. + */ + public RedisVectorStoreOptions() { + this(null); + } + + /** + * Creates a new builder. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the vector store record collection factory. + * + * @return the vector store record collection factory + */ + @Nullable + public RedisVectorStoreRecordCollectionFactory getVectorStoreRecordCollectionFactory() { + return vectorStoreRecordCollectionFactory; + } + + /** + * Builder for Redis vector store options. + */ + public static class Builder { + @Nullable + private RedisVectorStoreRecordCollectionFactory vectorStoreRecordCollectionFactory; + + /** + * Sets the vector store record collection factory. + * + * @param vectorStoreRecordCollectionFactory The vector store record collection factory. + * @return The updated builder instance. + */ + public Builder withVectorStoreRecordCollectionFactory( + RedisVectorStoreRecordCollectionFactory vectorStoreRecordCollectionFactory) { + this.vectorStoreRecordCollectionFactory = vectorStoreRecordCollectionFactory; + return this; + } + + /** + * Builds the options. + * + * @return The options. + */ + public RedisVectorStoreOptions build() { + return new RedisVectorStoreOptions(vectorStoreRecordCollectionFactory); + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordCollection.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordCollection.java new file mode 100644 index 00000000..8783320b --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordCollection.java @@ -0,0 +1,368 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.redis; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.semantickernel.data.VectorStoreRecordCollection; +import com.microsoft.semantickernel.data.VectorStoreRecordMapper; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDataField; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; +import com.microsoft.semantickernel.data.recordoptions.DeleteRecordOptions; +import com.microsoft.semantickernel.data.recordoptions.GetRecordOptions; +import com.microsoft.semantickernel.data.recordoptions.UpsertRecordOptions; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.json.JSONArray; +import org.json.JSONObject; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.Pipeline; +import redis.clients.jedis.Response; +import redis.clients.jedis.exceptions.JedisDataException; +import redis.clients.jedis.json.Path2; +import redis.clients.jedis.search.IndexDefinition; +import redis.clients.jedis.search.IndexOptions; +import redis.clients.jedis.search.Schema; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.AbstractMap.SimpleEntry; +import java.util.stream.Collectors; + +public class RedisVectorStoreRecordCollection + implements VectorStoreRecordCollection { + + private static final HashSet> supportedKeyTypes = new HashSet<>( + Collections.singletonList( + String.class)); + + private static final HashSet> supportedVectorTypes = new HashSet<>( + Arrays.asList( + List.class, + Collection.class)); + + private final JedisPooled client; + private final String collectionName; + private final RedisVectorStoreRecordCollectionOptions options; + private final VectorStoreRecordMapper> vectorStoreRecordMapper; + private final VectorStoreRecordDefinition recordDefinition; + private final Path2[] dataFields; + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * Creates a new instance of the RedisVectorRecordStore. + * + * @param client The Redis client. + * @param options The options for the store. + */ + @SuppressFBWarnings("EI_EXPOSE_REP2") + public RedisVectorStoreRecordCollection( + @Nonnull JedisPooled client, + @Nonnull String collectionName, + @Nonnull RedisVectorStoreRecordCollectionOptions options) { + this.client = client; + this.collectionName = collectionName; + this.options = options; + + // If record definition is not provided, create one from the record class + if (options.getRecordDefinition() == null) { + this.recordDefinition = VectorStoreRecordDefinition.fromRecordClass( + options.getRecordClass()); + } else { + this.recordDefinition = options.getRecordDefinition(); + } + + // Validate supported types + VectorStoreRecordDefinition.validateSupportedKeyTypes(options.getRecordClass(), + recordDefinition, supportedKeyTypes); + VectorStoreRecordDefinition.validateSupportedVectorTypes(options.getRecordClass(), + recordDefinition, supportedVectorTypes); + + // If mapper is not provided, set a default one + if (options.getVectorStoreRecordMapper() == null) { + vectorStoreRecordMapper = new RedisVectorStoreRecordMapper.Builder() + .withKeyFieldName(recordDefinition.getKeyField().getName()) + .withRecordClass(options.getRecordClass()) + .build(); + } else { + vectorStoreRecordMapper = options.getVectorStoreRecordMapper(); + } + + // Creates a list of paths to retrieve from Redis when no vectors are requested + // Paths are in the format of $.field + this.dataFields = recordDefinition.getDataFields().stream() + .map(VectorStoreRecordDataField::getName) + .map(Path2::new) + .toArray(Path2[]::new); + } + + /** + * Gets the name of the collection. + * + * @return The name of the collection. + */ + @Override + public String getCollectionName() { + return collectionName; + } + + /** + * Checks if the collection exists in the store. + * + * @return A Mono emitting a boolean indicating if the collection exists. + */ + @Override + public Mono collectionExistsAsync() { + return Mono.fromCallable(() -> { + try { + Map info = this.client.ftInfo(collectionName); + return info != null && !info.isEmpty(); + } catch (Exception e) { + if (!(e instanceof JedisDataException)) { + throw e; + } + return false; + } + }).subscribeOn(Schedulers.boundedElastic()); + } + + /** + * Creates the collection in the store. + * + * @return A Mono representing the completion of the creation operation. + */ + @Override + public Mono createCollectionAsync() { + return Mono.fromRunnable(() -> { + Schema schema = RedisVectorStoreCollectionCreateMapping + .mapToSchema(recordDefinition.getAllFields()); + + IndexDefinition indexDefinition = new IndexDefinition(IndexDefinition.Type.JSON) + .setPrefixes(collectionName + ":"); + + client.ftCreate( + collectionName, + IndexOptions.defaultOptions().setDefinition(indexDefinition), + schema); + }).subscribeOn(Schedulers.boundedElastic()).then(); + } + + @Override + public Mono createCollectionIfNotExistsAsync() { + return collectionExistsAsync().flatMap(exists -> { + if (!exists) { + return createCollectionAsync(); + } + + return Mono.empty(); + }); + } + + /** + * Deletes the collection from the store. + * + * @return A Mono representing the completion of the deletion operation. + */ + @Override + public Mono deleteCollectionAsync() { + return Mono.fromRunnable(() -> client.ftDropIndex(collectionName)) + .subscribeOn(Schedulers.boundedElastic()) + .then(); + } + + private String getRedisKey(String key, String collectionName) { + return options.isPrefixCollectionName() ? collectionName + ":" + key : key; + } + + private JsonNode removeRedisPathPrefix(JSONObject object) { + ObjectNode noPathPrefix = objectMapper.createObjectNode(); + object.keySet().forEach(key -> { + String newKey = key; + if (key.startsWith("$.")) { + newKey = key.substring(2); + } + + Object value = ((JSONArray) object.get(key)).get(0); + noPathPrefix.set(newKey, objectMapper.valueToTree(value)); + }); + + return noPathPrefix; + } + + /** + * Gets a record from the store. + * + * @param key The key of the record to get. + * @param options The options for getting the record. + * @return A Mono emitting the record. + */ + @Override + public Mono getAsync(String key, GetRecordOptions options) { + String redisKey = getRedisKey(key, collectionName); + + return Mono.defer(() -> { + try { + Object value; + if (options == null || options.includeVectors()) { + value = client.jsonGet(redisKey); + } else { + value = client.jsonGet(redisKey, dataFields); + } + + if (value == null) { + return Mono.empty(); + } + + JsonNode jsonNode; + if (options == null || options.includeVectors()) { + jsonNode = objectMapper.valueToTree(value); + } else { + // Remove the $. prefix from every key in the JSON object + jsonNode = removeRedisPathPrefix((JSONObject) value); + } + + return Mono.just(this.vectorStoreRecordMapper + .mapStorageModeltoRecord(new SimpleEntry<>(key, jsonNode))); + } catch (Exception e) { + return Mono.error(e); + } + }).subscribeOn(Schedulers.boundedElastic()); + } + + /** + * Gets a batch of records from the store. + * + * @param keys The keys of the records to get. + * @param options The options for getting the records. + * @return A Mono emitting a list of records. + */ + @Override + public Mono> getBatchAsync(List keys, + GetRecordOptions options) { + Pipeline pipeline = client.pipelined(); + List>> responses = new ArrayList<>(keys.size()); + keys.forEach(key -> { + String redisKey = getRedisKey(key, collectionName); + + if (options == null || options.includeVectors()) { + responses.add(new SimpleEntry<>(key, pipeline.jsonGet(redisKey))); + } else { + responses.add(new SimpleEntry<>(key, pipeline.jsonGet(redisKey, dataFields))); + } + }); + + return Mono.defer(() -> { + pipeline.sync(); + + try { + return Mono.just(responses.stream() + .map(entry -> { + Object value = entry.getValue().get(); + if (value == null) { + return null; + } + + JsonNode jsonNode; + if (options == null || options.includeVectors()) { + jsonNode = objectMapper.valueToTree(value); + } else { + jsonNode = removeRedisPathPrefix((JSONObject) value); + } + return this.vectorStoreRecordMapper + .mapStorageModeltoRecord(new SimpleEntry<>(entry.getKey(), jsonNode)); + }) + .collect(Collectors.toList())); + } catch (Exception e) { + return Mono.error(e); + } + }).subscribeOn(Schedulers.boundedElastic()); + } + + /** + * Inserts or updates a record in the store. + * + * @param data The record to upsert. + * @param options The options for upserting the record. + * @return A Mono emitting the key of the upserted record. + */ + @Override + public Mono upsertAsync(Record data, UpsertRecordOptions options) { + Entry redisObject = this.vectorStoreRecordMapper + .mapRecordToStorageModel(data); + String redisKey = getRedisKey(redisObject.getKey(), collectionName); + + return Mono.fromRunnable(() -> client.jsonSet(redisKey, redisObject.getValue())) + .subscribeOn(Schedulers.boundedElastic()) + .thenReturn(redisObject.getKey()); + } + + /** + * Inserts or updates a batch of records in the store. + * + * @param data The records to upsert. + * @param options The options for upserting the records. + * @return A Mono emitting a collection of keys of the upserted records. + */ + @Override + public Mono> upsertBatchAsync(List data, UpsertRecordOptions options) { + Pipeline pipeline = client.pipelined(); + List keys = new ArrayList<>(data.size()); + + data.forEach(record -> { + Entry redisObject = this.vectorStoreRecordMapper + .mapRecordToStorageModel(record); + String redisKey = getRedisKey(redisObject.getKey(), collectionName); + + keys.add(redisObject.getKey()); + pipeline.jsonSet(redisKey, redisObject.getValue()); + }); + + return Mono.fromRunnable(pipeline::sync) + .subscribeOn(Schedulers.boundedElastic()) + .thenReturn(keys); + } + + /** + * Deletes a record from the store. + * + * @param key The key of the record to delete. + * @param options The options for deleting the record. + * @return A Mono representing the completion of the deletion operation. + */ + @Override + public Mono deleteAsync(String key, DeleteRecordOptions options) { + String redisKey = getRedisKey(key, collectionName); + + return Mono.fromRunnable(() -> client.del(redisKey)) + .subscribeOn(Schedulers.boundedElastic()) + .then(); + } + + /** + * Deletes a batch of records from the store. + * + * @param strings The keys of the records to delete. + * @param options The options for deleting the records. + * @return A Mono representing the completion of the deletion operation. + */ + @Override + public Mono deleteBatchAsync(List strings, DeleteRecordOptions options) { + Pipeline pipeline = client.pipelined(); + strings.forEach(key -> { + String redisKey = getRedisKey(key, collectionName); + pipeline.del(redisKey); + }); + + return Mono.fromRunnable(pipeline::sync) + .subscribeOn(Schedulers.boundedElastic()) + .then(); + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordCollectionFactory.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordCollectionFactory.java new file mode 100644 index 00000000..45417980 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordCollectionFactory.java @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.redis; + +import redis.clients.jedis.JedisPooled; + +/** + * Factory for creating Redis vector store record collections. + * + */ +public interface RedisVectorStoreRecordCollectionFactory { + /** + * Creates a new vector store record collection. + * + * @param client The Redis client. + * @param collectionName The name of the collection. + * @param options The options for the collection. + * @return The collection. + */ + RedisVectorStoreRecordCollection createVectorStoreRecordCollection( + JedisPooled client, + String collectionName, + RedisVectorStoreRecordCollectionOptions options); +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordCollectionOptions.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordCollectionOptions.java new file mode 100644 index 00000000..93c30707 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordCollectionOptions.java @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.redis; + +import com.microsoft.semantickernel.data.VectorStoreRecordMapper; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Map.Entry; + +public class RedisVectorStoreRecordCollectionOptions { + private final Class recordClass; + @Nullable + private final VectorStoreRecordMapper> vectorStoreRecordMapper; + @Nullable + private final VectorStoreRecordDefinition recordDefinition; + private final boolean prefixCollectionName; + + private RedisVectorStoreRecordCollectionOptions( + @Nonnull Class recordClass, + @Nullable VectorStoreRecordMapper> vectorStoreRecordMapper, + @Nullable VectorStoreRecordDefinition recordDefinition, + boolean prefixCollectionName) { + this.recordClass = recordClass; + this.vectorStoreRecordMapper = vectorStoreRecordMapper; + this.recordDefinition = recordDefinition; + this.prefixCollectionName = prefixCollectionName; + } + + /** + * Creates a new builder. + * + * @param the record type + * @return the builder + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Gets the record class. + * + * @return the record class + */ + public Class getRecordClass() { + return recordClass; + } + + /** + * Gets the record definition. + * + * @return the record definition + */ + @Nullable + public VectorStoreRecordDefinition getRecordDefinition() { + return recordDefinition; + } + + /** + * Gets the vector store record mapper. + * + * @return the vector store record mapper + */ + @Nullable + public VectorStoreRecordMapper> getVectorStoreRecordMapper() { + return vectorStoreRecordMapper; + } + + /** + * Gets whether to prefix the collection name to the redis key. + * + * @return whether to prefix the collection name to the redis key + */ + public boolean isPrefixCollectionName() { + return prefixCollectionName; + } + + /** + * Builder for {@link RedisVectorStoreRecordCollectionOptions}. + * + * @param the record type + */ + public static class Builder { + @Nullable + private VectorStoreRecordMapper> vectorStoreRecordMapper; + @Nullable + private Class recordClass; + @Nullable + private VectorStoreRecordDefinition recordDefinition; + private boolean prefixCollectionName = true; + + /** + * Sets the record class. + * + * @param recordClass the record class + * @return the builder + */ + public Builder withRecordClass(Class recordClass) { + this.recordClass = recordClass; + return this; + } + + /** + * Sets the vector store record mapper. + * + * @param vectorStoreRecordMapper the vector store record mapper + * @return the builder + */ + public Builder withVectorStoreRecordMapper( + VectorStoreRecordMapper> vectorStoreRecordMapper) { + this.vectorStoreRecordMapper = vectorStoreRecordMapper; + return this; + } + + /** + * Sets the record definition. + * + * @param recordDefinition the record definition + * @return the builder + */ + public Builder withRecordDefinition(VectorStoreRecordDefinition recordDefinition) { + this.recordDefinition = recordDefinition; + return this; + } + + /** + * Sets whether to prefix the collection name to the redis key. + * Default is true. + * + * @param prefixCollectionName whether to prefix the collection name to the redis key + * @return the builder + */ + public Builder withPrefixCollectionName(boolean prefixCollectionName) { + this.prefixCollectionName = prefixCollectionName; + return this; + } + + /** + * Builds the options. + * + * @return the options + */ + public RedisVectorStoreRecordCollectionOptions build() { + if (recordClass == null) { + throw new IllegalArgumentException("recordClass must be provided"); + } + + return new RedisVectorStoreRecordCollectionOptions<>( + recordClass, + vectorStoreRecordMapper, + recordDefinition, + prefixCollectionName); + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordMapper.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordMapper.java new file mode 100644 index 00000000..a4f5f798 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/connectors/data/redis/RedisVectorStoreRecordMapper.java @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.connectors.data.redis; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.semantickernel.builders.SemanticKernelBuilder; +import com.microsoft.semantickernel.data.VectorStoreRecordMapper; +import com.microsoft.semantickernel.exceptions.SKException; + +import javax.annotation.Nullable; +import java.util.AbstractMap; +import java.util.Map.Entry; +import java.util.function.Function; + +public class RedisVectorStoreRecordMapper + extends VectorStoreRecordMapper> { + + private RedisVectorStoreRecordMapper( + Function> toStorageModelMapper, + Function, Record> toRecordMapper) { + super(toStorageModelMapper, toRecordMapper); + } + + /** + * Creates a new builder. + * + * @param the record type + * @return the builder + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Creates a new builder. + * + * @param the record type + */ + public static class Builder + implements SemanticKernelBuilder> { + @Nullable + private String keyFieldName; + @Nullable + private Class recordClass; + + /** + * Sets the key field name in the record. + * + * @param keyFieldName the key field + * @return the builder + */ + public Builder withKeyFieldName(String keyFieldName) { + this.keyFieldName = keyFieldName; + return this; + } + + /** + * Sets the record class. + * + * @param recordClass the record class + * @return the builder + */ + public Builder withRecordClass(Class recordClass) { + this.recordClass = recordClass; + return this; + } + + /** + * Builds the {@link RedisVectorStoreRecordMapper}. + * + * @return the {@link RedisVectorStoreRecordMapper} + */ + @Override + public RedisVectorStoreRecordMapper build() { + if (keyFieldName == null) { + throw new IllegalArgumentException("keyFieldName is required"); + } + if (recordClass == null) { + throw new IllegalArgumentException("recordClass is required"); + } + ObjectMapper mapper = new ObjectMapper(); + + return new RedisVectorStoreRecordMapper<>(record -> { + try { + ObjectNode jsonNode = mapper.valueToTree(record); + String key = jsonNode.get(keyFieldName).asText(); + jsonNode.remove(keyFieldName); + + return new AbstractMap.SimpleEntry<>(key, jsonNode); + } catch (Exception e) { + throw new SKException( + "Failure to serialize object, by default the Redis connector uses Jackson, ensure your model object can be serialized by Jackson, i.e the class is visible, has getters, constructor, annotations etc.", + e); + } + }, storageModel -> { + try { + ObjectNode jsonNode = mapper.valueToTree(storageModel.getValue()); + // Add the key back to the record + jsonNode.put(keyFieldName, storageModel.getKey()); + return mapper.convertValue(jsonNode, recordClass); + } catch (Exception e) { + throw new SKException( + "Failure to deserialize object, by default the Redis connector uses Jackson, ensure your model object can be serialized by Jackson, i.e the class is visible, has getters, constructor, annotations etc.", + e); + } + }); + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VectorStore.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VectorStore.java new file mode 100644 index 00000000..d778829a --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VectorStore.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data; + +import com.microsoft.semantickernel.data.VectorStoreRecordCollection; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +/** + * Represents a vector store. + * + * @param The type of the record collection. + */ +public interface VectorStore> { + + /** + * Gets a collection from the vector store. + * + * @param collectionName The name of the collection. + * @param recordClass The class type of the record. + * @param recordDefinition The record definition. + * @return The collection. + */ + RecordCollection getCollection( + @Nonnull String collectionName, + @Nonnull Class recordClass, + @Nullable VectorStoreRecordDefinition recordDefinition); + + /** + * Gets the names of all collections in the vector store. + * + * @return A list of collection names. + */ + Mono> getCollectionNamesAsync(); +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VectorStoreRecordCollection.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VectorStoreRecordCollection.java new file mode 100644 index 00000000..867cbf16 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VectorStoreRecordCollection.java @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data; + +import com.microsoft.semantickernel.data.recordoptions.DeleteRecordOptions; +import com.microsoft.semantickernel.data.recordoptions.GetRecordOptions; +import com.microsoft.semantickernel.data.recordoptions.UpsertRecordOptions; +import reactor.core.publisher.Mono; + +import java.util.List; + +public interface VectorStoreRecordCollection { + + /** + * Gets the name of the collection. + * + * @return The name of the collection. + */ + public String getCollectionName(); + + /** + * Checks if the collection exists in the store. + * + * @return A Mono emitting a boolean indicating if the collection exists. + */ + public Mono collectionExistsAsync(); + + /** + * Creates the collection in the store. + * + * @return A Mono representing the completion of the creation operation. + */ + public Mono createCollectionAsync(); + + /** + * Creates the collection in the store if it does not exist. + * + * @return A Mono representing the completion of the creation operation. + */ + public Mono createCollectionIfNotExistsAsync(); + + /** + * Deletes the collection from the store. + * + * @return A Mono representing the completion of the deletion operation. + */ + public Mono deleteCollectionAsync(); + + /** + * Gets a record from the store. + * + * @param key The key of the record to get. + * @param options The options for getting the record. + * @return A Mono emitting the record. + */ + Mono getAsync(Key key, GetRecordOptions options); + + /** + * Gets a batch of records from the store. + * + * @param keys The keys of the records to get. + * @param options The options for getting the records. + * @return A Mono emitting a list of records. + */ + Mono> getBatchAsync(List keys, GetRecordOptions options); + + /** + * Inserts or updates a record in the store. + * + * @param data The record to upsert. + * @param options The options for upserting the record. + * @return A Mono emitting the key of the upserted record. + */ + Mono upsertAsync(Record data, UpsertRecordOptions options); + + /** + * Inserts or updates a batch of records in the store. + * + * @param data The records to upsert. + * @param options The options for upserting the records. + * @return A Mono emitting a list of keys of the upserted records. + */ + Mono> upsertBatchAsync(List data, UpsertRecordOptions options); + + /** + * Deletes a record from the store. + * + * @param key The key of the record to delete. + * @param options The options for deleting the record. + * @return A Mono representing the completion of the deletion operation. + */ + Mono deleteAsync(Key key, DeleteRecordOptions options); + + /** + * Deletes a batch of records from the store. + * + * @param keys The keys of the records to delete. + * @param options The options for deleting the records. + * @return A Mono representing the completion of the deletion operation. + */ + Mono deleteBatchAsync(List keys, DeleteRecordOptions options); +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VectorStoreRecordMapper.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VectorStoreRecordMapper.java new file mode 100644 index 00000000..09420cf8 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VectorStoreRecordMapper.java @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data; + +import com.microsoft.semantickernel.builders.SemanticKernelBuilder; + +import java.util.function.Function; + +/** + * A mapper to convert between a record and a storage model. + * + * @param the record type + * @param the storage model type + */ +public class VectorStoreRecordMapper { + private final Function recordToStorageModelMapper; + private final Function storageModelToRecordMapper; + + /** + * Constructs a new instance of the VectorStoreRecordMapper. + * + * @param recordToStorageModelMapper the function to convert a record to a storage model + * @param storageModelToRecordMapper the function to convert a storage model to a record + */ + protected VectorStoreRecordMapper( + Function recordToStorageModelMapper, + Function storageModelToRecordMapper) { + this.recordToStorageModelMapper = recordToStorageModelMapper; + this.storageModelToRecordMapper = storageModelToRecordMapper; + } + + /** + * Gets the function to convert a record to a storage model. + * + * @return the function to convert a record to a storage model + */ + public Function getRecordToStorageModelMapper() { + return recordToStorageModelMapper; + } + + /** + * Gets the function to convert a storage model to a record. + * + * @return the function to convert a storage model to a record + */ + public Function getStorageModelToRecordMapper() { + return storageModelToRecordMapper; + } + + /** + * Converts a record to a storage model. + * + * @param record the record to convert + * @return the storage model + */ + public StorageModel mapRecordToStorageModel(Record record) { + return getRecordToStorageModelMapper().apply(record); + } + + /** + * Converts a storage model to a record. + * + * @param storageModel the storage model to convert + * @return the record + */ + public Record mapStorageModeltoRecord(StorageModel storageModel) { + return getStorageModelToRecordMapper().apply(storageModel); + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VolatileVectorStore.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VolatileVectorStore.java new file mode 100644 index 00000000..ef074247 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VolatileVectorStore.java @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data; + +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class VolatileVectorStore implements VectorStore> { + private final Map> collections; + + public VolatileVectorStore() { + this.collections = new ConcurrentHashMap<>(); + } + + /** + * Gets a collection from the vector store. + * + * @param collectionName The name of the collection. + * @param recordDefinition The record definition. + * @return The collection. + */ + @Override + public VolatileVectorStoreRecordCollection getCollection( + @Nonnull String collectionName, @Nonnull Class recordClass, + VectorStoreRecordDefinition recordDefinition) { + return new VolatileVectorStoreRecordCollection<>( + collectionName, + collections, + VolatileVectorStoreRecordCollectionOptions.builder() + .withRecordClass(recordClass) + .withRecordDefinition(recordDefinition) + .build()); + } + + /** + * Gets the names of all collections in the vector store. + * + * @return A list of collection names. + */ + @Override + public Mono> getCollectionNamesAsync() { + return Mono.just(new ArrayList<>(collections.keySet())); + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VolatileVectorStoreRecordCollection.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VolatileVectorStoreRecordCollection.java new file mode 100644 index 00000000..e675d4cd --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VolatileVectorStoreRecordCollection.java @@ -0,0 +1,220 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; +import com.microsoft.semantickernel.data.recordoptions.DeleteRecordOptions; +import com.microsoft.semantickernel.data.recordoptions.GetRecordOptions; +import com.microsoft.semantickernel.data.recordoptions.UpsertRecordOptions; +import com.microsoft.semantickernel.exceptions.SKException; +import reactor.core.publisher.Mono; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +public class VolatileVectorStoreRecordCollection + implements VectorStoreRecordCollection { + private static final HashSet> supportedKeyTypes = new HashSet<>( + Collections.singletonList(String.class)); + private Map> collections; + private final String collectionName; + private final VolatileVectorStoreRecordCollectionOptions options; + private final VectorStoreRecordDefinition recordDefinition; + private final ObjectMapper objectMapper; + + public VolatileVectorStoreRecordCollection(String collectionName, + VolatileVectorStoreRecordCollectionOptions options) { + this.collectionName = collectionName; + this.options = options; + this.collections = new ConcurrentHashMap<>(); + this.objectMapper = new ObjectMapper(); + + if (options.getRecordDefinition() != null) { + this.recordDefinition = options.getRecordDefinition(); + } else { + this.recordDefinition = VectorStoreRecordDefinition + .fromRecordClass(this.options.getRecordClass()); + } + + // Validate the key type + VectorStoreRecordDefinition.validateSupportedKeyTypes(options.getRecordClass(), + recordDefinition, supportedKeyTypes); + } + + VolatileVectorStoreRecordCollection(String collectionName, + Map> collections, + VolatileVectorStoreRecordCollectionOptions options) { + this(collectionName, options); + this.collections = collections; + } + + /** + * Gets the name of the collection. + * + * @return The name of the collection. + */ + @Override + public String getCollectionName() { + return collectionName; + } + + /** + * Checks if the collection exists in the store. + * + * @return A Mono emitting a boolean indicating if the collection exists. + */ + @Override + public Mono collectionExistsAsync() { + return Mono.fromCallable(() -> collections.containsKey(collectionName)); + } + + /** + * Creates the collection in the store. + * + * @return A Mono representing the completion of the creation operation. + */ + @Override + public Mono createCollectionAsync() { + return Mono.fromRunnable(() -> collections.put(collectionName, new ConcurrentHashMap<>())); + } + + /** + * Creates the collection in the store if it does not exist. + * + * @return A Mono representing the completion of the creation operation. + */ + @Override + public Mono createCollectionIfNotExistsAsync() { + return Mono + .fromRunnable(() -> collections.putIfAbsent(collectionName, new ConcurrentHashMap<>())); + } + + /** + * Deletes the collection from the store. + * + * @return A Mono representing the completion of the deletion operation. + */ + @Override + public Mono deleteCollectionAsync() { + return Mono.fromRunnable(() -> collections.remove(collectionName)); + } + + /** + * Gets a record from the store. + * + * @param key The key of the record to get. + * @param options The options for getting the record. + * @return A Mono emitting the record. + */ + @Override + public Mono getAsync(String key, GetRecordOptions options) { + return Mono.fromCallable(() -> getCollection().get(key)); + } + + /** + * Gets a batch of records from the store. + * + * @param keys The keys of the records to get. + * @param options The options for getting the records. + * @return A Mono emitting a list of records. + */ + @Override + public Mono> getBatchAsync(List keys, GetRecordOptions options) { + return Mono.fromCallable(() -> { + Map collection = getCollection(); + return keys.stream().map(collection::get).collect(Collectors.toList()); + }); + } + + /** + * Inserts or updates a record in the store. + * + * @param data The record to upsert. + * @param options The options for upserting the record. + * @return A Mono emitting the key of the upserted record. + */ + @Override + public Mono upsertAsync(Record data, UpsertRecordOptions options) { + return Mono.fromCallable(() -> { + try { + ObjectNode objectNode = objectMapper.valueToTree(data); + String key = objectNode.get(recordDefinition.getKeyField().getName()).asText(); + + getCollection().put(key, data); + return key; + } catch (Exception e) { + throw new SKException( + "Failure to serialize object. Ensure your model object can be serialized by Jackson, i.e the class is visible, has getters, constructor, annotations etc.", + e); + } + }); + } + + /** + * Inserts or updates a batch of records in the store. + * + * @param data The records to upsert. + * @param options The options for upserting the records. + * @return A Mono emitting a list of keys of the upserted records. + */ + @Override + public Mono> upsertBatchAsync(List data, UpsertRecordOptions options) { + return Mono.fromCallable(() -> { + Map collection = getCollection(); + return data.stream().map(record -> { + try { + ObjectNode objectNode = objectMapper.valueToTree(record); + String key = objectNode.get(recordDefinition.getKeyField().getName()).asText(); + + collection.put(key, record); + return key; + } catch (Exception e) { + throw new SKException( + "Failure to serialize object. Ensure your model object can be serialized by Jackson, i.e the class is visible, has getters, constructor, annotations etc.", + e); + } + }).collect(Collectors.toList()); + }); + } + + /** + * Deletes a record from the store. + * + * @param key The key of the record to delete. + * @param options The options for deleting the record. + * @return A Mono representing the completion of the deletion operation. + */ + @Override + public Mono deleteAsync(String key, DeleteRecordOptions options) { + return Mono.fromRunnable(() -> getCollection().remove(key)); + } + + /** + * Deletes a batch of records from the store. + * + * @param strings The keys of the records to delete. + * @param options The options for deleting the records. + * @return A Mono representing the completion of the deletion operation. + */ + @Override + public Mono deleteBatchAsync(List strings, DeleteRecordOptions options) { + return Mono.fromRunnable(() -> { + Map collection = getCollection(); + strings.forEach(collection::remove); + }); + } + + private Map getCollection() { + if (!collections.containsKey(collectionName)) { + throw new IllegalStateException( + String.format("Collection %s does not exist.", collectionName)); + } + return (Map) collections.get(collectionName); + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VolatileVectorStoreRecordCollectionOptions.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VolatileVectorStoreRecordCollectionOptions.java new file mode 100644 index 00000000..85bda03b --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/VolatileVectorStoreRecordCollectionOptions.java @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data; + +import com.microsoft.semantickernel.data.recorddefinition.VectorStoreRecordDefinition; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class VolatileVectorStoreRecordCollectionOptions { + private final Class recordClass; + @Nullable + private final VectorStoreRecordDefinition recordDefinition; + + /** + * Creates a new instance of the Volatile vector store record collection options. + * + * @param recordClass The record class. + * @param recordDefinition The record definition. + */ + public VolatileVectorStoreRecordCollectionOptions(@Nonnull Class recordClass, + @Nullable VectorStoreRecordDefinition recordDefinition) { + this.recordClass = recordClass; + this.recordDefinition = recordDefinition; + } + + /** + * Creates a new builder. + * + * @param the record type + * @return the builder + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Gets the record class. + * + * @return the record class + */ + public Class getRecordClass() { + return recordClass; + } + + /** + * Gets the record definition. + * + * @return the record definition + */ + public VectorStoreRecordDefinition getRecordDefinition() { + return recordDefinition; + } + + /** + * Builder for Volatile vector store record collection options. + * + * @param the record type + */ + public static class Builder { + @Nullable + private Class recordClass; + @Nullable + private VectorStoreRecordDefinition recordDefinition; + + /** + * Sets the record class. + * + * @param recordClass the record class + * @return the builder + */ + public Builder withRecordClass(Class recordClass) { + this.recordClass = recordClass; + return this; + } + + /** + * Sets the record definition. + * + * @param recordDefinition the record definition + * @return the builder + */ + public Builder withRecordDefinition(VectorStoreRecordDefinition recordDefinition) { + this.recordDefinition = recordDefinition; + return this; + } + + /** + * Builds the options. + * + * @return the options + */ + public VolatileVectorStoreRecordCollectionOptions build() { + if (recordClass == null) { + throw new IllegalArgumentException("recordClass is required"); + } + + return new VolatileVectorStoreRecordCollectionOptions<>(recordClass, recordDefinition); + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordattributes/VectorStoreRecordDataAttribute.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordattributes/VectorStoreRecordDataAttribute.java new file mode 100644 index 00000000..cc4d233d --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordattributes/VectorStoreRecordDataAttribute.java @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recordattributes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents a data attribute in a record. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface VectorStoreRecordDataAttribute { + /** + * Storage name of the field. + */ + String storageName() default ""; + + /** + * Whether the field has a vector representation. + */ + boolean hasEmbedding() default false; + + /** + * Name of the field that contains the vector representation. + */ + String embeddingFieldName() default ""; + + /** + * Whether the field is filterable. + */ + boolean isFilterable() default false; +} \ No newline at end of file diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordattributes/VectorStoreRecordKeyAttribute.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordattributes/VectorStoreRecordKeyAttribute.java new file mode 100644 index 00000000..ffe2451a --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordattributes/VectorStoreRecordKeyAttribute.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recordattributes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents the key attribute in a record. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface VectorStoreRecordKeyAttribute { + /** + * Storage name of the field. + */ + String storageName() default ""; +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordattributes/VectorStoreRecordVectorAttribute.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordattributes/VectorStoreRecordVectorAttribute.java new file mode 100644 index 00000000..3f404652 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordattributes/VectorStoreRecordVectorAttribute.java @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recordattributes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents a vector attribute in a record. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface VectorStoreRecordVectorAttribute { + + /** + * Number of dimensions in the vector. + */ + int dimensions(); + + /** + * Storage name of the field. + */ + String storageName() default ""; + + /** + * Type of index to be used for the vector. + */ + String indexKind() default ""; + + /** + * Distance function to be used for to compute the distance between vectors. + */ + String distanceFunction() default ""; +} \ No newline at end of file diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/DistanceFunction.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/DistanceFunction.java new file mode 100644 index 00000000..b41aa32e --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/DistanceFunction.java @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recorddefinition; + +public enum DistanceFunction { + COSINE_SIMILARITY("cosineSimilarity"), DOT_PRODUCT("dotProduct"), EUCLIDEAN("euclidean"); + + private final String value; + + DistanceFunction(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + /** + * Converts a string to a DistanceFunction. + * If the string is null or empty, the method returns DistanceFunction.COSINE_SIMILARITY. + * + * @param text the string to convert + * @return the DistanceFunction + */ + public static DistanceFunction fromString(String text) { + if (text == null || text.isEmpty()) { + return COSINE_SIMILARITY; + } + + for (DistanceFunction b : DistanceFunction.values()) { + if (b.value.equalsIgnoreCase(text)) { + return b; + } + } + throw new IllegalArgumentException("No distance function with value " + text + " found"); + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/IndexKind.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/IndexKind.java new file mode 100644 index 00000000..62832de2 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/IndexKind.java @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recorddefinition; + +public enum IndexKind { + HNSW("Hnsw"), FLAT("Flat"); + + private final String value; + + IndexKind(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + /** + * Converts a string to an IndexKind. + * If the string is null or empty, the method returns IndexKind.FLAT. + * + * @param text the string to convert + * @return the IndexKind + */ + public static IndexKind fromString(String text) { + if (text == null || text.isEmpty()) { + return FLAT; + } + + for (IndexKind b : IndexKind.values()) { + if (b.value.equalsIgnoreCase(text)) { + return b; + } + } + throw new IllegalArgumentException("No index kind with value " + text + " found"); + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordDataField.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordDataField.java new file mode 100644 index 00000000..af43821a --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordDataField.java @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recorddefinition; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class VectorStoreRecordDataField extends VectorStoreRecordField { + private final boolean hasEmbedding; + @Nullable + private final String embeddingFieldName; + @Nullable + private final Class fieldType; + private final boolean isFilterable; + + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a new instance of the VectorStoreRecordDataField class. + * + * @param name the name of the field + * @param storageName the storage name of the field + * @param hasEmbedding a value indicating whether the field has an embedding + * @param embeddingFieldName the name of the embedding + * @param fieldType the field type + * @param isFilterable a value indicating whether the field is filterable + */ + public VectorStoreRecordDataField( + @Nonnull String name, + @Nullable String storageName, + boolean hasEmbedding, + @Nullable String embeddingFieldName, + @Nullable Class fieldType, + boolean isFilterable) { + super(name, storageName); + this.hasEmbedding = hasEmbedding; + this.embeddingFieldName = embeddingFieldName; + this.fieldType = fieldType; + this.isFilterable = isFilterable; + } + + /** + * Gets a value indicating whether the field has an embedding. + * + * @return a value indicating whether the field has an embedding + */ + public boolean hasEmbedding() { + return hasEmbedding; + } + + /** + * Gets the name of the embedding. + * + * @return the name of the embedding + */ + @Nullable + public String getEmbeddingFieldName() { + return embeddingFieldName; + } + + /** + * Gets the field type. + * + * @return the field type + */ + @Nullable + public Class getFieldType() { + return fieldType; + } + + /** + * Gets a value indicating whether the field is filterable. + * + * @return a value indicating whether the field is filterable + */ + public boolean isFilterable() { + return isFilterable; + } + + public static class Builder + extends VectorStoreRecordField.Builder { + private boolean hasEmbedding; + @Nullable + private String embeddingFieldName; + @Nullable + private Class fieldType; + private boolean isFilterable; + + /** + * Sets a value indicating whether the field has an embedding. + * + * @param hasEmbedding a value indicating whether the field has an embedding + * @return the builder + */ + public Builder withHasEmbedding(boolean hasEmbedding) { + this.hasEmbedding = hasEmbedding; + return this; + } + + /** + * Sets the name of the embedding field. + * + * @param embeddingFieldName the name of the embedding + * @return the builder + */ + public Builder withEmbeddingFieldName(String embeddingFieldName) { + this.embeddingFieldName = embeddingFieldName; + return this; + } + + /** + * Sets the field type. + * + * @param fieldType the field type + * @return the builder + */ + public Builder withFieldType(Class fieldType) { + this.fieldType = fieldType; + return this; + } + + /** + * Sets a value indicating whether the field is filterable. + * + * @param isFilterable a value indicating whether the field is filterable + * @return the builder + */ + public Builder withIsFilterable(boolean isFilterable) { + this.isFilterable = isFilterable; + return this; + } + + /** + * Builds a new instance of the VectorStoreRecordDataField class. + * + * @return a new instance of the VectorStoreRecordDataField class + */ + @Override + public VectorStoreRecordDataField build() { + if (name == null) { + throw new IllegalArgumentException("name is required"); + } + if (hasEmbedding && embeddingFieldName == null) { + throw new IllegalArgumentException( + "embeddingFieldName is required when hasEmbedding is true"); + } + + return new VectorStoreRecordDataField( + name, + storageName, + hasEmbedding, + embeddingFieldName, + fieldType, + isFilterable); + } + } + +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordDefinition.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordDefinition.java new file mode 100644 index 00000000..a1914d2c --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordDefinition.java @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recorddefinition; + +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordDataAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordKeyAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordVectorAttribute; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Represents a definition of a vector store record. + */ +public class VectorStoreRecordDefinition { + private final VectorStoreRecordKeyField keyField; + private final List dataFields; + private final List vectorFields; + + public VectorStoreRecordKeyField getKeyField() { + return keyField; + } + + public List getDataFields() { + return Collections.unmodifiableList(dataFields); + } + + /** + * Gets the vector fields in the record definition. + * @return List of VectorStoreRecordVectorField + */ + public List getVectorFields() { + return Collections.unmodifiableList(vectorFields); + } + + /** + * Gets all fields in the record definition. + * @return List of VectorStoreRecordField + */ + public List getAllFields() { + List fields = new ArrayList<>(); + fields.add(keyField); + fields.addAll(dataFields); + fields.addAll(vectorFields); + return fields; + } + + private VectorStoreRecordDefinition( + VectorStoreRecordKeyField keyField, + List dataFields, + List vectorFields) { + this.keyField = keyField; + this.dataFields = dataFields; + this.vectorFields = vectorFields; + } + + private static VectorStoreRecordDefinition checkFields( + List keyFields, + List dataFields, + List vectorFields) { + if (keyFields.size() != 1) { + throw new IllegalArgumentException("Exactly one key field is required"); + } + + return new VectorStoreRecordDefinition(keyFields.iterator().next(), dataFields, + vectorFields); + } + + /** + * Create a VectorStoreRecordDefinition from a collection of fields. + * @param fields The fields to create the definition from. + * @return VectorStoreRecordDefinition + */ + public static VectorStoreRecordDefinition fromFields(List fields) { + List keyFields = fields.stream() + .filter(p -> p instanceof VectorStoreRecordKeyField) + .map(p -> (VectorStoreRecordKeyField) p) + .collect(Collectors.toList()); + + List dataFields = fields.stream() + .filter(p -> p instanceof VectorStoreRecordDataField) + .map(p -> (VectorStoreRecordDataField) p) + .collect(Collectors.toList()); + + List vectorFields = fields.stream() + .filter(p -> p instanceof VectorStoreRecordVectorField) + .map(p -> (VectorStoreRecordVectorField) p) + .collect(Collectors.toList()); + + return checkFields(keyFields, dataFields, vectorFields); + } + + /** + * Create a VectorStoreRecordDefinition from a model. + * @param recordClass The model class to create the definition from. + * @return VectorStoreRecordDefinition + */ + public static VectorStoreRecordDefinition fromRecordClass(Class recordClass) { + List keyFields = new ArrayList<>(); + List dataFields = new ArrayList<>(); + List vectorFields = new ArrayList<>(); + + for (Field field : recordClass.getDeclaredFields()) { + if (field.isAnnotationPresent(VectorStoreRecordKeyAttribute.class)) { + VectorStoreRecordKeyAttribute keyAttribute = field + .getAnnotation(VectorStoreRecordKeyAttribute.class); + + keyFields.add(VectorStoreRecordKeyField.builder() + .withName(field.getName()) + .withStorageName(keyAttribute.storageName()) + .build()); + } + + if (field.isAnnotationPresent(VectorStoreRecordDataAttribute.class)) { + VectorStoreRecordDataAttribute dataAttribute = field + .getAnnotation(VectorStoreRecordDataAttribute.class); + + dataFields.add(VectorStoreRecordDataField.builder() + .withName(field.getName()) + .withStorageName(dataAttribute.storageName()) + .withHasEmbedding(dataAttribute.hasEmbedding()) + .withEmbeddingFieldName(dataAttribute.embeddingFieldName()) + .withFieldType(field.getType()) + .withIsFilterable(dataAttribute.isFilterable()) + .build()); + } + + if (field.isAnnotationPresent(VectorStoreRecordVectorAttribute.class)) { + VectorStoreRecordVectorAttribute vectorAttribute = field + .getAnnotation(VectorStoreRecordVectorAttribute.class); + + vectorFields.add(VectorStoreRecordVectorField.builder() + .withName(field.getName()) + .withStorageName(vectorAttribute.storageName()) + .withDimensions(vectorAttribute.dimensions()) + .withIndexKind(IndexKind.fromString(vectorAttribute.indexKind())) + .withDistanceFunction( + DistanceFunction.fromString(vectorAttribute.distanceFunction())) + .build()); + } + } + + return checkFields(keyFields, dataFields, vectorFields); + } + + private static String getSupportedTypesString(@Nullable HashSet> types) { + if (types == null || types.isEmpty()) { + return ""; + } + return types.stream().map(Class::getName).collect(Collectors.joining(", ")); + } + + public static void validateSupportedKeyTypes(@Nonnull Class recordClass, + @Nonnull VectorStoreRecordDefinition recordDefinition, + @Nonnull HashSet> supportedTypes) { + String supportedTypesString = getSupportedTypesString(supportedTypes); + + try { + Field declaredField = recordClass.getDeclaredField(recordDefinition.keyField.getName()); + + if (!supportedTypes.contains(declaredField.getType())) { + throw new IllegalArgumentException( + "Unsupported key field type: " + declaredField.getType().getName() + + ". Supported types are: " + supportedTypesString); + } + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException( + "Key field not found in record class: " + recordDefinition.keyField.getName()); + } + } + + public static void validateSupportedDataTypes(@Nonnull Class recordClass, + @Nonnull VectorStoreRecordDefinition recordDefinition, + @Nonnull HashSet> supportedTypes) { + String supportedTypesString = getSupportedTypesString(supportedTypes); + + for (VectorStoreRecordDataField field : recordDefinition.dataFields) { + try { + Field declaredField = recordClass.getDeclaredField(field.getName()); + + if (!supportedTypes.contains(declaredField.getType())) { + throw new IllegalArgumentException( + "Unsupported data field type: " + declaredField.getType().getName() + + ". Supported types are: " + supportedTypesString); + } + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException( + "Data field not found in record class: " + field.getName()); + } + } + } + + public static void validateSupportedVectorTypes(@Nonnull Class recordClass, + @Nonnull VectorStoreRecordDefinition recordDefinition, + @Nonnull HashSet> supportedTypes) { + String supportedTypesString = getSupportedTypesString(supportedTypes); + + for (VectorStoreRecordVectorField field : recordDefinition.vectorFields) { + try { + Field declaredField = recordClass.getDeclaredField(field.getName()); + + if (!supportedTypes.contains(declaredField.getType())) { + throw new IllegalArgumentException( + "Unsupported vector field type: " + declaredField.getType().getName() + + ". Supported types are: " + supportedTypesString); + } + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException( + "Vector field not found in record class: " + field.getName()); + } + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordField.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordField.java new file mode 100644 index 00000000..c27c7a06 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordField.java @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recorddefinition; + +import com.microsoft.semantickernel.builders.SemanticKernelBuilder; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Represents a field in a record. + */ +public class VectorStoreRecordField { + private final String name; + @Nullable + private final String storageName; + + /** + * Creates a new instance of the VectorStoreRecordField class. + * + * @param name the name of the field + */ + public VectorStoreRecordField(@Nonnull String name, + @Nullable String storageName) { + this.name = name; + this.storageName = storageName; + } + + /** + * Gets the name of the field. + * + * @return the name of the field + */ + public String getName() { + return name; + } + + /** + * Gets the storage name of the field. + * + * @return the storage name of the field + */ + @Nullable + public String getStorageName() { + return storageName; + } + + public abstract static class Builder> + implements SemanticKernelBuilder { + protected String name = ""; + protected String storageName = ""; + + /** + * Sets the name of the field. + * + * @param name the name of the field + * @return the builder + */ + public U withName(String name) { + this.name = name; + return (U) this; + } + + /** + * Sets the storage name of the field. + * + * @param storageName the storage name of the field + * @return the builder + */ + public U withStorageName(String storageName) { + this.storageName = storageName; + return (U) this; + } + + /** + * Builds the field. + * + * @return the field + */ + @Override + public abstract T build(); + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordKeyField.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordKeyField.java new file mode 100644 index 00000000..4502aa3b --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordKeyField.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recorddefinition; + +/** + * Represents the key field in a record. + */ +public class VectorStoreRecordKeyField extends VectorStoreRecordField { + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a new instance of the VectorStoreRecordKeyField class. + * + * @param name the name of the field + */ + public VectorStoreRecordKeyField(String name, String storageName) { + super(name, storageName); + } + + public static class Builder + extends VectorStoreRecordField.Builder { + @Override + public VectorStoreRecordKeyField build() { + return new VectorStoreRecordKeyField(name, storageName); + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordVectorField.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordVectorField.java new file mode 100644 index 00000000..34653c84 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recorddefinition/VectorStoreRecordVectorField.java @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recorddefinition; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Represents a vector field in a record. + */ +public class VectorStoreRecordVectorField extends VectorStoreRecordField { + private final int dimensions; + @Nullable + private final IndexKind indexKind; + @Nullable + private final DistanceFunction distanceFunction; + + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a new instance of the VectorStoreRecordVectorField class. + * + * @param name the name of the field + * @param storageName the storage name of the field + * @param dimensions the number of dimensions in the vector + * @param indexKind the index kind + * @param distanceFunction the distance function + */ + public VectorStoreRecordVectorField( + @Nonnull String name, + @Nullable String storageName, + int dimensions, + @Nullable IndexKind indexKind, + @Nullable DistanceFunction distanceFunction) { + super(name, storageName); + this.dimensions = dimensions; + this.indexKind = indexKind; + this.distanceFunction = distanceFunction; + } + + /** + * Gets the number of dimensions in the vector. + * + * @return the number of dimensions in the vector + */ + public int getDimensions() { + return dimensions; + } + + /** + * Gets the index kind. + * + * @return the index kind + */ + @Nullable + public IndexKind getIndexKind() { + return indexKind; + } + + /** + * Gets the distance function. + * + * @return the distance function + */ + @Nullable + public DistanceFunction getDistanceFunction() { + return distanceFunction; + } + + public static class Builder + extends VectorStoreRecordField.Builder { + private int dimensions; + @Nullable + private IndexKind indexKind; + @Nullable + private DistanceFunction distanceFunction; + + /** + * Sets the number of dimensions in the vector. + * + * @param dimensions the number of dimensions in the vector + * @return the builder + */ + public Builder withDimensions(int dimensions) { + this.dimensions = dimensions; + return this; + } + + /** + * Sets the index kind. + * + * @param indexKind the index kind + * @return the builder + */ + public Builder withIndexKind(IndexKind indexKind) { + this.indexKind = indexKind; + return this; + } + + /** + * Sets the distance function. + * + * @param distanceFunction the distance function + * @return the builder + */ + public Builder withDistanceFunction(DistanceFunction distanceFunction) { + this.distanceFunction = distanceFunction; + return this; + } + + /** + * Builds a new instance of the VectorStoreRecordVectorField class. + * + * @return a new instance of the VectorStoreRecordVectorField class + */ + @Override + public VectorStoreRecordVectorField build() { + if (name == null) { + throw new IllegalArgumentException("name is required"); + } + if (dimensions <= 0) { + throw new IllegalArgumentException("dimensions must be greater than 0"); + } + + return new VectorStoreRecordVectorField(name, storageName, dimensions, indexKind, + distanceFunction); + } + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordoptions/DeleteRecordOptions.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordoptions/DeleteRecordOptions.java new file mode 100644 index 00000000..ad68a90f --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordoptions/DeleteRecordOptions.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recordoptions; + +/** + * Options for deleting a record. + */ +public class DeleteRecordOptions { +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordoptions/GetRecordOptions.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordoptions/GetRecordOptions.java new file mode 100644 index 00000000..6c81b5c9 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordoptions/GetRecordOptions.java @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recordoptions; + +import com.microsoft.semantickernel.builders.SemanticKernelBuilder; + +/** + * Options for getting a record. + */ +public class GetRecordOptions { + private final boolean includeVectors; + + private GetRecordOptions(boolean includeVectors) { + this.includeVectors = includeVectors; + } + + /** + * Creates a new builder. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder implements SemanticKernelBuilder { + private boolean includeVectors; + + /** + * Sets whether to include vectors. + * + * @param includeVectors whether to include vectors + * @return GetRecordOptions.Builder + */ + public Builder includeVectors(boolean includeVectors) { + this.includeVectors = includeVectors; + return this; + } + + /** + * Builds the options. + * + * @return GetRecordOptions + */ + @Override + public GetRecordOptions build() { + return new GetRecordOptions(includeVectors); + } + } + + /** + * Gets whether to include vectors. + * + * @return whether to include vectors + */ + public boolean includeVectors() { + return includeVectors; + } +} diff --git a/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordoptions/UpsertRecordOptions.java b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordoptions/UpsertRecordOptions.java new file mode 100644 index 00000000..132eaf92 --- /dev/null +++ b/semantickernel-experimental/src/main/java/com/microsoft/semantickernel/data/recordoptions/UpsertRecordOptions.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data.recordoptions; + +/** + * Options for upserting a record. + */ +public class UpsertRecordOptions { +} diff --git a/semantickernel-experimental/src/test/java/com/microsoft/semantickernel/data/Hotel.java b/semantickernel-experimental/src/test/java/com/microsoft/semantickernel/data/Hotel.java new file mode 100644 index 00000000..6e895714 --- /dev/null +++ b/semantickernel-experimental/src/test/java/com/microsoft/semantickernel/data/Hotel.java @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data; + +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordDataAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordKeyAttribute; +import com.microsoft.semantickernel.data.recordattributes.VectorStoreRecordVectorAttribute; + +import java.util.List; + +public class Hotel { + @VectorStoreRecordKeyAttribute + private final String id; + @VectorStoreRecordDataAttribute + private final String name; + @VectorStoreRecordDataAttribute + private final int code; + @VectorStoreRecordDataAttribute(hasEmbedding = true, embeddingFieldName = "descriptionEmbedding") + private final String description; + @VectorStoreRecordVectorAttribute(dimensions = 3) + private final List descriptionEmbedding; + @VectorStoreRecordDataAttribute + private final double rating; + + public Hotel() { + this(null, null, 0, null, null, 0.0); + } + + public Hotel(String id, String name, int code, String description, + List descriptionEmbedding, double rating) { + this.id = id; + this.name = name; + this.code = code; + this.description = description; + this.descriptionEmbedding = descriptionEmbedding; + this.rating = rating; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public List getDescriptionEmbedding() { + return descriptionEmbedding; + } + + public double getRating() { + return rating; + } +} diff --git a/semantickernel-experimental/src/test/java/com/microsoft/semantickernel/data/VolatileVectorStoreRecordCollectionTest.java b/semantickernel-experimental/src/test/java/com/microsoft/semantickernel/data/VolatileVectorStoreRecordCollectionTest.java new file mode 100644 index 00000000..9a087adb --- /dev/null +++ b/semantickernel-experimental/src/test/java/com/microsoft/semantickernel/data/VolatileVectorStoreRecordCollectionTest.java @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class VolatileVectorStoreRecordCollectionTest { + + private static VolatileVectorStoreRecordCollection recordCollection; + + @BeforeAll + public static void setup() { + recordCollection = new VolatileVectorStoreRecordCollection<>( + "hotels", + VolatileVectorStoreRecordCollectionOptions.builder() + .withRecordClass(Hotel.class) + .build()); + recordCollection.createCollectionIfNotExistsAsync().block(); + } + + @BeforeEach + public void clearCollection() { + recordCollection.deleteCollectionAsync().block(); + recordCollection.createCollectionAsync().block(); + } + + private List getHotels() { + return Arrays.asList( + new Hotel("id_1", "Hotel 1", 1, "Hotel 1 description", Arrays.asList(1.0f, 2.0f, 3.0f), + 4.0), + new Hotel("id_2", "Hotel 2", 2, "Hotel 2 description", Arrays.asList(1.0f, 2.0f, 3.0f), + 3.0), + new Hotel("id_3", "Hotel 3", 3, "Hotel 3 description", Arrays.asList(1.0f, 2.0f, 3.0f), + 5.0), + new Hotel("id_4", "Hotel 4", 4, "Hotel 4 description", Arrays.asList(1.0f, 2.0f, 3.0f), + 4.0), + new Hotel("id_5", "Hotel 5", 5, "Hotel 5 description", Arrays.asList(1.0f, 2.0f, 3.0f), + 5.0)); + } + + @Test + public void createAndDeleteCollectionAsync() { + assertEquals(true, recordCollection.collectionExistsAsync().block()); + + recordCollection.deleteCollectionAsync().block(); + assertEquals(false, recordCollection.collectionExistsAsync().block()); + + recordCollection.createCollectionAsync().block(); + assertEquals(true, recordCollection.collectionExistsAsync().block()); + } + + @Test + public void upsertRecordAsync() { + List hotels = getHotels(); + for (Hotel hotel : hotels) { + recordCollection.upsertAsync(hotel, null).block(); + } + + for (Hotel hotel : hotels) { + Hotel retrievedHotel = recordCollection.getAsync(hotel.getId(), null).block(); + assertNotNull(retrievedHotel); + assertEquals(hotel.getId(), retrievedHotel.getId()); + assertEquals(hotel.getName(), retrievedHotel.getName()); + assertEquals(hotel.getDescription(), retrievedHotel.getDescription()); + } + } + + @Test + public void upsertBatchAsync() { + List hotels = getHotels(); + recordCollection.upsertBatchAsync(hotels, null).block(); + + for (Hotel hotel : hotels) { + Hotel retrievedHotel = recordCollection.getAsync(hotel.getId(), null).block(); + assertNotNull(retrievedHotel); + assertEquals(hotel.getId(), retrievedHotel.getId()); + assertEquals(hotel.getName(), retrievedHotel.getName()); + assertEquals(hotel.getDescription(), retrievedHotel.getDescription()); + } + } + + @Test + public void getBatchAsync() { + List hotels = getHotels(); + recordCollection.upsertBatchAsync(hotels, null).block(); + + List keys = hotels.stream().map(Hotel::getId).collect(Collectors.toList()); + List retrievedHotels = recordCollection.getBatchAsync(keys, null).block(); + + assertNotNull(retrievedHotels); + assertEquals(keys.size(), retrievedHotels.size()); + for (Hotel hotel : retrievedHotels) { + assertTrue(keys.contains(hotel.getId())); + } + } + + @Test + public void deleteRecordAsync() { + List hotels = getHotels(); + recordCollection.upsertBatchAsync(hotels, null).block(); + + for (Hotel hotel : hotels) { + recordCollection.deleteAsync(hotel.getId(), null).block(); + assertNull(recordCollection.getAsync(hotel.getId(), null).block()); + } + } + + @Test + public void deleteBatchAsync() { + List hotels = getHotels(); + recordCollection.upsertBatchAsync(hotels, null).block(); + + List keys = hotels.stream().map(Hotel::getId).collect(Collectors.toList()); + recordCollection.deleteBatchAsync(keys, null).block(); + + for (String key : keys) { + assertNull(recordCollection.getAsync(key, null).block()); + } + } +} diff --git a/semantickernel-experimental/src/test/java/com/microsoft/semantickernel/data/VolatileVectorStoreTest.java b/semantickernel-experimental/src/test/java/com/microsoft/semantickernel/data/VolatileVectorStoreTest.java new file mode 100644 index 00000000..cfd52757 --- /dev/null +++ b/semantickernel-experimental/src/test/java/com/microsoft/semantickernel/data/VolatileVectorStoreTest.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.data; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +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 VolatileVectorStoreTest { + private static VolatileVectorStore vectorStore; + + @BeforeAll + public static void setup() { + vectorStore = new VolatileVectorStore(); + } + + @Test + public void collectionNamesAsync() { + List collectionNames = Arrays.asList("hotels1", "hotels2", "hotels3"); + + for (String collectionName : collectionNames) { + vectorStore.getCollection(collectionName, Hotel.class, null).createCollectionAsync() + .block(); + } + + List retrievedCollectionNames = vectorStore.getCollectionNamesAsync().block(); + assertNotNull(retrievedCollectionNames); + assertEquals(collectionNames.size(), retrievedCollectionNames.size()); + for (String collectionName : collectionNames) { + assertTrue(retrievedCollectionNames.contains(collectionName)); + } + } +} diff --git a/java/spotbugs-exclude.xml b/spotbugs-exclude.xml similarity index 98% rename from java/spotbugs-exclude.xml rename to spotbugs-exclude.xml index 5fd588b2..e8a2357a 100644 --- a/java/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -1,4 +1,3 @@ -