├── .github ├── .codecov.yml ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── documentation-issue.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── draft-release-notes-config.yml └── workflows │ ├── CI.yml │ ├── add-untriaged.yml │ ├── backport.yml │ ├── backwards_compatibility_tests_workflow.yml │ ├── create-documentation-issue.yml │ ├── delete_backport_branch.yml │ ├── dependabot_pr.yml │ ├── draft-release-notes-workflow.yml │ ├── links.yml │ └── pr_stats.yml ├── .gitignore ├── .whitesource ├── ADMINS.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOPER_GUIDE.md ├── LICENSE ├── MAINTAINERS.md ├── NOTICE ├── README.md ├── RELEASING.md ├── RELEVANCE.md ├── SECURITY.md ├── amazon-kendra-intelligent-ranking ├── build.gradle └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── opensearch │ │ │ └── search │ │ │ └── relevance │ │ │ ├── AmazonKendraIntelligentRankingPlugin.java │ │ │ ├── actionfilter │ │ │ └── SearchActionFilter.java │ │ │ ├── client │ │ │ └── OpenSearchClient.java │ │ │ ├── configuration │ │ │ ├── ConfigurationUtils.java │ │ │ ├── Constants.java │ │ │ ├── ResultTransformerConfiguration.java │ │ │ ├── ResultTransformerConfigurationFactory.java │ │ │ ├── SearchConfigurationExtBuilder.java │ │ │ └── TransformerConfiguration.java │ │ │ └── transformer │ │ │ ├── ResultTransformer.java │ │ │ ├── TransformerType.java │ │ │ └── kendraintelligentranking │ │ │ ├── KendraIntelligentRanker.java │ │ │ ├── client │ │ │ ├── KendraClientSettings.java │ │ │ ├── KendraHttpClient.java │ │ │ ├── SimpleAwsErrorHandler.java │ │ │ └── SimpleResponseHandler.java │ │ │ ├── configuration │ │ │ ├── Constants.java │ │ │ ├── KendraIntelligentRankerSettings.java │ │ │ ├── KendraIntelligentRankingConfiguration.java │ │ │ └── KendraIntelligentRankingConfigurationFactory.java │ │ │ ├── model │ │ │ ├── KendraIntelligentRankingException.java │ │ │ ├── PassageScore.java │ │ │ └── dto │ │ │ │ ├── Document.java │ │ │ │ ├── RescoreRequest.java │ │ │ │ ├── RescoreResult.java │ │ │ │ └── RescoreResultItem.java │ │ │ ├── pipeline │ │ │ └── KendraRankingResponseProcessor.java │ │ │ └── preprocess │ │ │ ├── BM25Scorer.java │ │ │ ├── PassageGenerator.java │ │ │ ├── QueryParser.java │ │ │ ├── SentenceSplitter.java │ │ │ └── TextTokenizer.java │ └── plugin-metadata │ │ └── plugin-security.policy │ ├── test │ └── java │ │ └── org │ │ └── opensearch │ │ └── search │ │ └── relevance │ │ ├── AmazonKendraIntelligentRankingPluginIT.java │ │ ├── SearchRelevanceTests.java │ │ ├── actionfilter │ │ └── SearchActionFilterTests.java │ │ ├── configuration │ │ └── SearchConfigurationExtBuilderTests.java │ │ └── transformer │ │ └── kendraintelligentranking │ │ ├── KendraIntelligentRankerTests.java │ │ ├── client │ │ ├── KendraClientSettingsTests.java │ │ ├── KendraHttpClientTests.java │ │ ├── KendraIntelligentClientTests.java │ │ └── SimpleAwsErrorHandlerTests.java │ │ ├── configuration │ │ └── KendraIntelligentRankingConfigurationTests.java │ │ ├── model │ │ ├── KendraIntelligentRankingExceptionTests.java │ │ └── dto │ │ │ └── DocumentTests.java │ │ ├── pipeline │ │ └── KendraRankingResponseProcessorTests.java │ │ └── preprocess │ │ ├── BM25ScorerTests.java │ │ ├── PassageGeneratorTests.java │ │ ├── QueryParserTests.java │ │ ├── SentenceSplitterTests.java │ │ └── TextTokenizerTests.java │ └── yamlRestTest │ ├── java │ └── org │ │ └── opensearch │ │ └── search │ │ └── relevance │ │ └── AmazonKendraIntelligentRankingClientYamlTestSuiteIT.java │ └── resources │ └── rest-api-spec │ └── test │ └── 10_basic.yml ├── amazon-personalize-ranking ├── build.gradle └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── opensearch │ │ │ └── search │ │ │ └── relevance │ │ │ ├── AmazonPersonalizeRankingPlugin.java │ │ │ └── transformer │ │ │ └── personalizeintelligentranking │ │ │ ├── PersonalizeRankingResponseProcessor.java │ │ │ ├── client │ │ │ ├── PersonalizeClient.java │ │ │ ├── PersonalizeClientSettings.java │ │ │ └── PersonalizeCredentialsProviderFactory.java │ │ │ ├── configuration │ │ │ ├── Constants.java │ │ │ └── PersonalizeIntelligentRankerConfiguration.java │ │ │ ├── requestparameter │ │ │ ├── PersonalizeRequestParameterUtil.java │ │ │ ├── PersonalizeRequestParameters.java │ │ │ └── PersonalizeRequestParametersExtBuilder.java │ │ │ ├── reranker │ │ │ ├── PersonalizedRanker.java │ │ │ ├── PersonalizedRankerFactory.java │ │ │ └── impl │ │ │ │ └── AmazonPersonalizedRankerImpl.java │ │ │ └── utils │ │ │ └── ValidationUtil.java │ └── plugin-metadata │ │ └── plugin-security.policy │ ├── test │ └── java │ │ └── org │ │ └── opensearch │ │ └── search │ │ └── relevance │ │ ├── AmazonPersonalizeRankingPluginIT.java │ │ └── transformer │ │ └── personalizeintelligentranking │ │ ├── PersonalizeRankingResponseProcessorTests.java │ │ ├── client │ │ ├── PersonalizeClientSettingsTests.java │ │ ├── PersonalizeClientTests.java │ │ └── PersonalizeCredentialsProviderFactoryTests.java │ │ ├── configuration │ │ └── PersonalizeIntelligentRankerConfigurationTests.java │ │ ├── ranker │ │ ├── PersonalizeRankerFactoryTests.java │ │ └── impl │ │ │ └── AmazonPersonalizeRankerImplTests.java │ │ ├── requestparameter │ │ ├── PersonalizeRequestParameterUtilTests.java │ │ └── PersonalizeRequestParametersExtBuilderTests.java │ │ └── utils │ │ ├── PersonalizeClientSettingsTestUtil.java │ │ ├── PersonalizeRuntimeTestUtil.java │ │ ├── SearchTestUtil.java │ │ └── ValidationUtilTests.java │ └── yamlRestTest │ ├── java │ └── org │ │ └── opensearch │ │ └── search │ │ └── relevance │ │ └── AmazonPersonalizeRankingClientYamlTestSuiteIT.java │ └── resources │ └── rest-api-spec │ └── test │ └── 10_basic.yml ├── build.gradle ├── docs └── plan │ └── SearchRelevancyWorkbench.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── helpers ├── personalized_search_ranking_quickstart.sh └── search_processing_kendra_quickstart.sh ├── release-notes ├── search-processor.release-notes-2.4.0.md ├── search-processor.release-notes-2.5.0.md ├── search-processor.release-notes-2.6.0.md ├── search-processor.release-notes-2.7.0.md ├── search-processor.release-notes-2.8.0.md └── search-processor.release-notes-2.9.0.md └── settings.gradle /.github/.codecov.yml: -------------------------------------------------------------------------------- 1 | # https://docs.codecov.com/docs/codecov-yaml 2 | coverage: 3 | status: 4 | project: 5 | default: 6 | # https://docs.codecov.com/docs/commit-status#target 7 | target: auto # coverage must be equal or above the previous commit -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This should match the MAINTAINERS.md file in the root of this repo 2 | * @msfroh @macohen @noCharger @mingshl @sejli -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug report 3 | about: Create a report to help us improve 4 | title: '[BUG]' 5 | labels: 'bug, untriaged' 6 | assignees: '' 7 | --- 8 | 9 | **What is the bug?** 10 | A clear and concise description of the bug. 11 | 12 | **How can one reproduce the bug?** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **What is the expected behavior?** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **What is your host/environment?** 23 | - OS: [e.g. iOS] 24 | - Version [e.g. 22] 25 | - Plugins 26 | 27 | **Do you have any screenshots?** 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Do you have any additional context?** 31 | Add any other context about the problem. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: OpenSearch Community Support 3 | url: https://discuss.opendistrocommunity.dev/ 4 | about: Please ask and answer questions here. 5 | - name: AWS/Amazon Security 6 | url: https://aws.amazon.com/security/vulnerability-reporting/ 7 | about: Please report security vulnerabilities here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation-issue.md: -------------------------------------------------------------------------------- 1 | **Is your feature request related to a problem?** 2 | A new feature has been added. 3 | 4 | **What solution would you like?** 5 | Document the usage of the new feature. 6 | 7 | **What alternatives have you considered?** 8 | N/A 9 | 10 | **Do you have any additional context?** 11 | See please 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🎆 Feature request 3 | about: Request a feature in this project 4 | title: '[FEATURE]' 5 | labels: 'enhancement, untriaged' 6 | assignees: '' 7 | --- 8 | **Is your feature request related to a problem?** 9 | A clear and concise description of what the problem is, e.g. _I'm always frustrated when [...]_ 10 | 11 | **What solution would you like?** 12 | A clear and concise description of what you want to happen. 13 | 14 | **What alternatives have you considered?** 15 | A clear and concise description of any alternative solutions or features you've considered. 16 | 17 | **Do you have any additional context?** 18 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | [Describe what this change achieves] 3 | 4 | ### Issues Resolved 5 | [List any issues this PR will resolve] 6 | 7 | ### Check List 8 | - [ ] New functionality includes testing. 9 | - [ ] All tests pass 10 | - [ ] New functionality has been documented. 11 | - [ ] New functionality has javadoc added 12 | - [ ] Commits are signed as per the DCO using --signoff 13 | 14 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 15 | For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/search-processor/blob/main/CONTRIBUTING.md). 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot 2 | updates: 3 | - directory: / 4 | open-pull-requests-limit: 1 5 | package-ecosystem: gradle 6 | schedule: 7 | interval: daily 8 | labels: 9 | - "dependabot" 10 | - "dependencies" 11 | version: 2 -------------------------------------------------------------------------------- /.github/draft-release-notes-config.yml: -------------------------------------------------------------------------------- 1 | # The overall template of the release notes 2 | template: | 3 | Compatible with OpenSearch (**set version here**). 4 | $CHANGES 5 | 6 | # Setting the formatting and sorting for the release notes body 7 | name-template: Version (set version here) 8 | change-template: '* $TITLE (#$NUMBER)' 9 | sort-by: merged_at 10 | sort-direction: ascending 11 | replacers: 12 | - search: '##' 13 | replace: '###' 14 | 15 | # Organizing the tagged PRs into categories 16 | categories: 17 | - title: 'Breaking Changes' 18 | labels: 19 | - 'breaking' 20 | - title: 'Security' 21 | labels: 22 | - 'security fix' 23 | - title: 'Features' 24 | labels: 25 | - 'feature' 26 | - title: 'Enhancements' 27 | labels: 28 | - 'enhancement' 29 | - title: 'Bug Fixes' 30 | labels: 31 | - 'bug' 32 | - title: 'Infrastructure' 33 | labels: 34 | - 'infrastructure' 35 | - title: 'Documentation' 36 | labels: 37 | - 'documentation' 38 | - title: 'Maintenance' 39 | labels: 40 | - 'maintenance' 41 | - title: 'Refactoring' 42 | labels: 43 | - 'refactoring' -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test Search Request Processor 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' # every night 5 | push: 6 | branches: 7 | - "*" 8 | - "feature/**" 9 | pull_request: 10 | branches: 11 | - "*" 12 | - "feature/**" 13 | 14 | jobs: 15 | Build-search-request-processor: 16 | strategy: 17 | matrix: 18 | java: [21] 19 | os: [ubuntu-latest, macos-latest, windows-latest] 20 | 21 | name: Build and Test Search Request Processor Plugin 22 | runs-on: ${{ matrix.os }} 23 | 24 | steps: 25 | - name: Checkout Search Request Processor 26 | uses: actions/checkout@v1 27 | 28 | - name: Setup Java ${{ matrix.java }} 29 | uses: actions/setup-java@v1 30 | with: 31 | java-version: ${{ matrix.java }} 32 | 33 | - name: Run build Windows 34 | if: ${{ matrix.os == 'windows-latest' }} 35 | run: | 36 | ./gradlew.bat build 37 | 38 | - name: Run non-Windows 39 | if: ${{ matrix.os != 'windows-latest' }} 40 | run: | 41 | ./gradlew build 42 | 43 | - name: Upload Coverage Report 44 | if: ${{matrix.os}} == 'ubuntu' 45 | uses: codecov/codecov-action@v1 46 | with: 47 | token: ${{ secrets.CODECOV_TOKEN }} 48 | 49 | 50 | -------------------------------------------------------------------------------- /.github/workflows/add-untriaged.yml: -------------------------------------------------------------------------------- 1 | name: Apply 'untriaged' label during issue lifecycle 2 | 3 | on: 4 | issues: 5 | types: [opened, reopened, transferred] 6 | 7 | jobs: 8 | apply-label: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/github-script@v6 12 | with: 13 | script: | 14 | github.rest.issues.addLabels({ 15 | issue_number: context.issue.number, 16 | owner: context.repo.owner, 17 | repo: context.repo.repo, 18 | labels: ['untriaged'] 19 | }) 20 | -------------------------------------------------------------------------------- /.github/workflows/backport.yml: -------------------------------------------------------------------------------- 1 | name: Backport 2 | on: 3 | pull_request_target: 4 | types: 5 | - closed 6 | - labeled 7 | 8 | jobs: 9 | backport: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | name: Backport 15 | steps: 16 | - name: GitHub App token 17 | id: github_app_token 18 | uses: tibdex/github-app-token@v1.5.0 19 | with: 20 | app_id: ${{ secrets.APP_ID }} 21 | private_key: ${{ secrets.APP_PRIVATE_KEY }} 22 | installation_id: 22958780 23 | 24 | - name: Backport 25 | uses: VachaShah/backport@v1.1.4 26 | with: 27 | github_token: ${{ steps.github_app_token.outputs.token }} 28 | branch_name: backport/backport-${{ github.event.number }} 29 | -------------------------------------------------------------------------------- /.github/workflows/backwards_compatibility_tests_workflow.yml: -------------------------------------------------------------------------------- 1 | name: Backwards Compatibility Tests Search Request Processor 2 | on: 3 | push: 4 | branches: 5 | - "*" 6 | - "feature/**" 7 | pull_request: 8 | branches: 9 | - "*" 10 | - "feature/**" 11 | 12 | jobs: 13 | Restart-Upgrade-BWCTests-k-NN: 14 | strategy: 15 | matrix: 16 | java: [ 21 ] 17 | bwc_version : [ "2.7.0" ] 18 | opensearch_version : [ "3.0.0-SNAPSHOT" ] 19 | 20 | name: SRP Restart-Upgrade BWC Tests 21 | runs-on: ubuntu-latest 22 | env: 23 | BWC_VERSION_RESTART_UPGRADE: ${{ matrix.bwc_version }} 24 | 25 | steps: 26 | - name: Checkout SRP 27 | uses: actions/checkout@v1 28 | 29 | - name: Setup Java ${{ matrix.java }} 30 | uses: actions/setup-java@v1 31 | with: 32 | java-version: ${{ matrix.java }} 33 | 34 | # Comments intentional; these workflow scripts are copied from k-NN and we are reverse engineering them; missing qa directory fails the workflow here 35 | # - name: Run SRP Restart-Upgrade BWC Tests from BWCVersion-${{ matrix.bwc_version }} to OpenSearch Version-${{ matrix.opensearch_version }} 36 | # run: | 37 | # echo "Running restart-upgrade backwards compatibility tests ..." 38 | # ./gradlew :qa:restart-upgrade:testRestartUpgrade -Dtests.bwc.version=$BWC_VERSION_RESTART_UPGRADE 39 | 40 | 41 | Rolling-Upgrade-BWCTests-SRP: 42 | strategy: 43 | matrix: 44 | java: [ 21 ] 45 | bwc_version: [ "2.11.1" ] 46 | opensearch_version: [ "3.0.0-SNAPSHOT" ] 47 | 48 | name: SRP Rolling-Upgrade BWC Tests 49 | runs-on: ubuntu-latest 50 | env: 51 | BWC_VERSION_ROLLING_UPGRADE: ${{ matrix.bwc_version }} 52 | 53 | steps: 54 | - name: Checkout SRP 55 | uses: actions/checkout@v1 56 | 57 | - name: Setup Java ${{ matrix.java }} 58 | uses: actions/setup-java@v1 59 | with: 60 | java-version: ${{ matrix.java }} 61 | 62 | # - name: Install dependencies 63 | # run: | 64 | # sudo apt-get install libopenblas-dev gfortran -y 65 | 66 | # - name: Run SRP Rolling-Upgrade BWC Tests from BWCVersion-${{ matrix.bwc_version }} to OpenSearch Version-${{ matrix.opensearch_version }} 67 | # run: | 68 | # echo "Running rolling-upgrade backwards compatibility tests ..." 69 | # ./gradlew :qa:rolling-upgrade:testRollingUpgrade -Dtests.bwc.version=$BWC_VERSION_ROLLING_UPGRADE 70 | -------------------------------------------------------------------------------- /.github/workflows/create-documentation-issue.yml: -------------------------------------------------------------------------------- 1 | name: Create Documentation Issue 2 | on: 3 | pull_request: 4 | types: 5 | - labeled 6 | env: 7 | PR_NUMBER: ${{ github.event.number }} 8 | 9 | jobs: 10 | create-issue: 11 | if: ${{ github.event.label.name == 'needs-documentation' }} 12 | runs-on: ubuntu-latest 13 | name: Create Documentation Issue 14 | steps: 15 | - name: GitHub App token 16 | id: github_app_token 17 | uses: tibdex/github-app-token@v1.5.0 18 | with: 19 | app_id: ${{ secrets.APP_ID }} 20 | private_key: ${{ secrets.APP_PRIVATE_KEY }} 21 | installation_id: 22958780 22 | 23 | - name: Checkout code 24 | uses: actions/checkout@v2 25 | 26 | - name: Edit the issue template 27 | run: | 28 | echo "https://github.com/opensearch-project/search-processor/pull/${{ env.PR_NUMBER }}." >> ./.github/ISSUE_TEMPLATE/documentation-issue.md 29 | 30 | - name: Create Issue From File 31 | id: create-issue 32 | uses: peter-evans/create-issue-from-file@v4 33 | with: 34 | title: Add documentation related to new feature 35 | content-filepath: ./.github/ISSUE_TEMPLATE/documentation-issue.md 36 | labels: documentation 37 | repository: opensearch-project/documentation-website 38 | token: ${{ steps.github_app_token.outputs.token }} 39 | 40 | - name: Print Issue 41 | run: echo Created related documentation issue ${{ steps.create-issue.outputs.issue-number }} 42 | -------------------------------------------------------------------------------- /.github/workflows/delete_backport_branch.yml: -------------------------------------------------------------------------------- 1 | name: Delete merged branch of the backport PRs 2 | on: 3 | pull_request: 4 | types: 5 | - closed 6 | 7 | jobs: 8 | delete-branch: 9 | runs-on: ubuntu-latest 10 | if: startsWith(github.event.pull_request.head.ref,'backport-') 11 | steps: 12 | - name: Delete merged branch 13 | uses: SvanBoxel/delete-merged-branch@main 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/dependabot_pr.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot PR actions 2 | on: pull_request 3 | 4 | jobs: 5 | dependabot: 6 | runs-on: ubuntu-latest 7 | permissions: 8 | pull-requests: write 9 | contents: write 10 | if: ${{ github.actor == 'dependabot[bot]' }} 11 | steps: 12 | - name: GitHub App token 13 | id: github_app_token 14 | uses: tibdex/github-app-token@v1.5.0 15 | with: 16 | app_id: ${{ secrets.APP_ID }} 17 | private_key: ${{ secrets.APP_PRIVATE_KEY }} 18 | installation_id: 22958780 19 | 20 | - name: Check out code 21 | uses: actions/checkout@v2 22 | with: 23 | token: ${{ steps.github_app_token.outputs.token }} 24 | 25 | - name: Update Gradle SHAs 26 | run: | 27 | ./gradlew updateSHAs 28 | 29 | - name: Commit the changes 30 | uses: stefanzweifel/git-auto-commit-action@v4.7.2 31 | with: 32 | commit_message: Updating SHAs 33 | branch: ${{ github.head_ref }} 34 | commit_user_name: dependabot[bot] 35 | commit_user_email: support@github.com 36 | commit_options: '--signoff' 37 | 38 | - name: Commit the changes 39 | uses: stefanzweifel/git-auto-commit-action@v4.7.2 40 | with: 41 | commit_message: Spotless formatting 42 | branch: ${{ github.head_ref }} 43 | commit_user_name: dependabot[bot] 44 | commit_user_email: support@github.com 45 | commit_options: '--signoff' 46 | 47 | - name: Update the changelog 48 | uses: dangoslen/dependabot-changelog-helper@v1 49 | with: 50 | version: 'Unreleased' 51 | 52 | - name: Commit the changes 53 | uses: stefanzweifel/git-auto-commit-action@v4 54 | with: 55 | commit_message: "Update changelog" 56 | branch: ${{ github.head_ref }} 57 | commit_user_name: dependabot[bot] 58 | commit_user_email: support@github.com 59 | commit_options: '--signoff' -------------------------------------------------------------------------------- /.github/workflows/draft-release-notes-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_release_draft: 10 | name: Update draft release notes 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Update draft release notes 14 | uses: release-drafter/release-drafter@v5 15 | with: 16 | config-name: draft-release-notes-config.yml 17 | name: Version (set here) 18 | tag: (None) 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/links.yml: -------------------------------------------------------------------------------- 1 | name: Link Checker 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | 8 | jobs: 9 | linkchecker: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: lychee Link Checker 16 | id: lychee 17 | uses: lycheeverse/lychee-action@master 18 | with: 19 | args: --accept=200,403,429 **/*.html **/*.md **/*.txt **/*.json 20 | env: 21 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 22 | - name: Fail if there were link errors 23 | run: exit ${{ steps.lychee.outputs.exit_code }} -------------------------------------------------------------------------------- /.github/workflows/pr_stats.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Stats 2 | 3 | on: 4 | pull_request: 5 | types: [opened] 6 | 7 | jobs: 8 | stats: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | steps: 14 | - name: Run pull request stats 15 | uses: flowwer-dev/pull-request-stats@master 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | **/build/ 3 | !src/**/build/ 4 | 5 | # Ignore Gradle GUI config 6 | gradle-app.setting 7 | 8 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 9 | !gradle-wrapper.jar 10 | 11 | # Avoid ignore Gradle wrappper properties 12 | !gradle-wrapper.properties 13 | 14 | # Cache of project 15 | .gradletasknamecache 16 | 17 | # Eclipse Gradle plugin generated files 18 | # Eclipse Core 19 | .project 20 | # JDT-specific (Eclipse Java Development Tools) 21 | .classpath 22 | 23 | # Intellij files 24 | .idea/ 25 | 26 | # Compiled class file 27 | *.class 28 | 29 | # Log file 30 | *.log 31 | 32 | # BlueJ files 33 | *.ctxt 34 | 35 | # Mobile Tools for Java (J2ME) 36 | .mtj.tmp/ 37 | 38 | # Package Files # 39 | *.jar 40 | *.war 41 | *.nar 42 | *.ear 43 | *.zip 44 | *.tar.gz 45 | *.rar 46 | 47 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 48 | hs_err_pid* 49 | replay_pid* 50 | 51 | *~ 52 | 53 | # temporary files which can be created if a process still has a handle open of a deleted file 54 | .fuse_hidden* 55 | 56 | # KDE directory preferences 57 | .directory 58 | 59 | # Linux trash folder which might appear on any partition or disk 60 | .Trash-* 61 | 62 | # .nfs files are created when an open file is removed but is still being accessed 63 | .nfs* 64 | 65 | # General 66 | .DS_Store 67 | .AppleDouble 68 | .LSOverride 69 | 70 | # Icon must end with two \r 71 | Icon 72 | 73 | 74 | # Thumbnails 75 | ._* 76 | 77 | # Files that might appear in the root of a volume 78 | .DocumentRevisions-V100 79 | .fseventsd 80 | .Spotlight-V100 81 | .TemporaryItems 82 | .Trashes 83 | .VolumeIcon.icns 84 | .com.apple.timemachine.donotpresent 85 | 86 | # Directories potentially created on remote AFP share 87 | .AppleDB 88 | .AppleDesktop 89 | Network Trash Folder 90 | Temporary Items 91 | .apdisk 92 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "configMode": "AUTO", 4 | "configExternalURL": "", 5 | "projectToken": "", 6 | "baseBranches": [] 7 | }, 8 | "checkRunSettings": { 9 | "vulnerableCheckRunConclusionLevel": "failure", 10 | "displayMode": "diff", 11 | "useMendCheckNames": true 12 | }, 13 | "issueSettings": { 14 | "minSeverityLevel": "LOW", 15 | "issueType": "DEPENDENCY" 16 | }, 17 | "remediateSettings": { 18 | "workflowRules": { 19 | "enabled": true 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /ADMINS.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This document explains who the admins are (see below), what they do in this repo, and how they should be doing it. If you're interested in becoming a maintainer, see [MAINTAINERS](MAINTAINERS.md). If you're interested in contributing, see [CONTRIBUTING](CONTRIBUTING.md). 4 | 5 | ## Current Admins 6 | 7 | | Admin | GitHub ID | Affiliation | 8 | | --------------- | --------------------------------------- | ----------- | 9 | | Charlotte Henkle| [CEHENKLE](https://github.com/CEHENKLE) | Amazon | 10 | | Henri Yandell | [hyandell](https://github.com/hyandell) | Amazon | 11 | | Anirudha Jadhav | [anirudha](https://github.com/anirudha) | Amazon | 12 | | Mark Cohen | [macohen](https://github.com/macohen) | Amazon | 13 | 14 | ## Admin Responsibilities 15 | 16 | As an admin you own stewardship of the repository and its settings. Admins have [admin-level permissions on a repository](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization). Use those privileges to serve the community and protect the repository as follows. 17 | 18 | ### Prioritize Security 19 | 20 | Security is your number one priority. Manage security keys and safeguard access to the repository. 21 | 22 | Note that this repository is monitored and supported 24/7 by Amazon Security, see [Reporting a Vulnerability](SECURITY.md) for details. 23 | 24 | ### Enforce Code of Conduct 25 | 26 | Act on [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) violations by revoking access, and blocking malicious actors. 27 | 28 | ### Adopt Organizational Best Practices 29 | 30 | Adopt organizational best practices, work in the open, and collaborate with other admins by opening issues before making process changes. Prefer consistency, and avoid diverging from practices in the opensearch-project organization. -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | This code of conduct applies to all spaces provided by the OpenSource project including in code, documentation, issue trackers, mailing lists, chat channels, wikis, blogs, social media and any other communication channels used by the project. 3 | 4 | 5 | **Our open source communities endeavor to:** 6 | 7 | * Be Inclusive: We are committed to being a community where everyone can join and contribute. This means using inclusive and welcoming language. 8 | * Be Welcoming: We are committed to maintaining a safe space for everyone to be able to contribute. 9 | * Be Respectful: We are committed to encouraging differing viewpoints, accepting constructive criticism and work collaboratively towards decisions that help the project grow. Disrespectful and unacceptable behavior will not be tolerated. 10 | * Be Collaborative: We are committed to supporting what is best for our community and users. When we build anything for the benefit of the project, we should document the work we do and communicate to others on how this affects their work. 11 | 12 | 13 | **Our Responsibility. As contributors, members, or bystanders we each individually have the responsibility to behave professionally and respectfully at all times. Disrespectful and unacceptable behaviors include, but are not limited to:** 14 | 15 | * The use of violent threats, abusive, discriminatory, or derogatory language; 16 | * Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, race, political or religious affiliation; 17 | * Posting of sexually explicit or violent content; 18 | * The use of sexualized language and unwelcome sexual attention or advances; 19 | * Public or private harassment of any kind; 20 | * Publishing private information, such as physical or electronic address, without permission; 21 | * Other conduct which could reasonably be considered inappropriate in a professional setting; 22 | * Advocating for or encouraging any of the above behaviors. 23 | * Enforcement and Reporting Code of Conduct Issues: 24 | 25 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported. [Contact us](mailto:opensource-codeofconduct@amazon.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to this Project 2 | 3 | OpenSearch is a community project that is built and maintained by people just like **you**. 4 | [This document](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md) explains how you can contribute to this and related projects. 5 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This document contains a list of maintainers in this repo. See [opensearch-project/.github/RESPONSIBILITIES.md](https://github.com/opensearch-project/.github/blob/main/RESPONSIBILITIES.md#maintainer-responsibilities) that explains what the role of maintainer means, what maintainers do in this and other repos, and how they should be doing it. If you're interested in contributing, and becoming a maintainer, see [CONTRIBUTING](CONTRIBUTING.md). 4 | 5 | ## Current Maintainers 6 | 7 | | Maintainer | GitHub ID | Affiliation | 8 | | --------------- | ----------------------------------------- | ----------- | 9 | | Michael Froh | [msfroh](https://github.com/msfroh) | Amazon | 10 | | Mark Cohen | [macohen](https://github.com/macohen) | Amazon | 11 | | Louis Chu | [noCharger](https://github.com/noCharger) | Amazon | 12 | | Mingshi Liu | [mingshl](https://github.com/mingshl) | Amazon | 13 | | Sean Li | [sejli](https://github.com/sejli) | Amazon | 14 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | OpenSearch (https://opensearch.org/) 2 | Copyright OpenSearch Contributors 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build and Test Search Query & Request Transformers](https://github.com/opensearch-project/search-processor/actions/workflows/CI.yml/badge.svg)](https://github.com/opensearch-project/search-processor/actions/workflows/CI.yml) 2 | [![codecov](https://codecov.io/gh/opensearch-project/search-processor/branch/main/graph/badge.svg?token=PYQO2GW39S)](https://codecov.io/gh/opensearch-project/search-processor) 3 | ![PRs welcome!](https://img.shields.io/badge/PRs-welcome!-success) 4 | 5 | # Search Rerankers: AWS Kendra & AWS Personalize 6 | - [Welcome!](#welcome) 7 | - [Project Resources](#project-resources) 8 | - [Code of Conduct](#code-of-conduct) 9 | - [License](#license) 10 | - [Copyright](#copyright) 11 | 12 | ## Welcome! 13 | This repository hosts the code for two self-install re-rankers that integrate into [Search Pipelines](https://opensearch.org/docs/latest/search-plugins/search-pipelines/index/). User documentation for the Personalize Reranker is [here](https://opensearch.org/docs/latest/search-plugins/search-pipelines/personalize-search-ranking/). For Kendra, it is [here](https://opensearch.org/docs/latest/search-plugins/search-relevance/index/#reranking-results-with-kendra-intelligent-ranking-for-opensearch). 14 | 15 | # Search Processors: Where Do They Go? 16 | The current guideline for developing processors is that if you are developing a processor that would introduce new dependencies in [OpenSearch Core](https://github.com/opensearch-project/OpenSearch) (e.g. new libraries, makes a network connection outside of OpenSearch), it should be in a separate repository. Please consider creating it in a standalone repository since each processor should be thought of like a \*NIX command with input and output connected by pipes (i.e. a Search Pipeline). Each processor should do one thing and do it well. Otherwise, it could go into the OpenSearch repository under [org.opensearch.search.pipeline.common](https://github.com/opensearch-project/OpenSearch/tree/a08d588691c3b232e65d73b0a0c2fc5c72c870cf/modules/search-pipeline-common). If you have doubts, just create an issue in OpenSearch Core and, if you have one, a new PR. Maintainers will help guide you. 17 | 18 | 19 | # History 20 | This repository has also been used for discussion and ideas around search relevance. These discussions still exist here, however due to the relatively new standard of having one repo per plugin in OpenSearch and our implementations beginning to make it into the OpenSearch build, we have two repositories now. This repository will develop into a plugin that will allow OpenSearch users to rewrite search queries, rerank results, and log data about those actions. The other repository, [dashboards-search-relevance](https://www.github.com/opensearch-projects/dashboards-search-relevance), is where we will build front-end tooling to help relevance engineers and business users tune results. 21 | 22 | ## Project Resources 23 | 24 | * [OpenSearch Project Website](https://opensearch.org/) 25 | * [Downloads](https://opensearch.org/downloads.html) 26 | * [Project Principles](https://opensearch.org/#principles) 27 | * [Search Pipelines](https://opensearch.org/docs/latest/search-plugins/search-pipelines/index/) 28 | * [Contributing to OpenSearch Search Request Processor](CONTRIBUTING.md) 29 | * [Search Relevance](RELEVANCE.md) 30 | * [Maintainer Responsibilities](MAINTAINERS.md) 31 | * [Release Management](RELEASING.md) 32 | * [Admin Responsibilities](ADMINS.md) 33 | * [Security](SECURITY.md) 34 | 35 | 36 | ## Code of Conduct 37 | 38 | This project has adopted the [Amazon Open Source Code of Conduct](CODE_OF_CONDUCT.md). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq), or contact [opensource-codeofconduct@amazon.com](mailto:opensource-codeofconduct@amazon.com) with any additional questions or comments. 39 | 40 | ## License 41 | 42 | This project is licensed under the [Apache v2.0 License](LICENSE). 43 | 44 | ## Copyright 45 | 46 | Copyright OpenSearch Contributors. See [NOTICE](NOTICE) for details. 47 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | This project follows the [OpenSearch release process](https://github.com/opensearch-project/.github/blob/main/RELEASING.md). -------------------------------------------------------------------------------- /RELEVANCE.md: -------------------------------------------------------------------------------- 1 | # Search Relevance 2 | 3 | ## Overview 4 | 5 | In a search engine, the relevance is the measure of the relationship accuracy between the search query and the search result. Higher the relevance is, the higher is the quality of search result and the users are able to get more relevant content. This project aims to add plugins to OpenSearch to help users make their query results more accurate, contextual and relevant. 6 | 7 | ## Relevancy and OpenSearch 8 | 9 | Today, OpenSearch provides results in the order of scores generated by algorithms matching the indexed document contents to the input query. The documents that are more relevant to the query are ranked higher than the ones that are less relevant. These rankings may make sense to one set of users/applications and for others it may be very irrelevant. For example, relevancy for an E-commerce company can mean more similar products in the same category of the search query. While for a document search relevancy may mean, searching the query across different topics/categories present in the document store. This is why, we need more ways to customize the results and its rankings as per the need of the user/business. 10 | 11 | ## Relevancy Engineering 12 | 13 | Relevancy as a problem, can’t just be solved at the search layer. Improving relevancy should be envisioned holistically from understanding the ingested data and usage signals to extracting feature, adding re-writers and improving algorithms. Below is the architecture of OpenSearch Relevancy Engineering. 14 | 15 |

16 | 17 |

18 | [Initially presented at Haystack 2022 by @anirudha , @JohannesDaniel and @ps48]. 19 |
20 |
21 | Overall the Relevancy Engineering can be divided into two tiers: 22 | 23 | 1. **Ingestion Tier:** This tier handles getting the data from different sources to OpenSearch. This data may include: 24 | 1. Search Data: 25 | 1. Core search data, that needs to be queried on by OpenSearch 26 | 2. Ingestion connectors to fetch the data from different data sources and sink in OpenSearch indices. 27 | 2. Search Management Data: 28 | 1. Adding rules and judgements to the rewriter indices. 29 | 3. Observability Data: 30 | 1. Adding customer usage signals to OpenSearch, these signals may include granular details like anonymized customer queries, clicks, orders and session details. 31 | 2. **Search & Relevancy Platform Tier:** This tier is responsible for analytics, re-wrtiers, model improvements and adding search configurations. 32 | 1. Search Analytics & Discovery: 33 | 1. Dashboards for analytics, metrics for search tests, search UIs and query profiling. 34 | 2. Querqy based query Rewriting: 35 | 1. Rewriters to customize queries with synonyms, word-breaks, spell corrections, query relaxation. 36 | 3. Search Back Office: 37 | 1. Manage business rules, ontologies and manual judgments. 38 | 4. Relevancy workbench: 39 | 1. Improve algorithms with automated testing, relevance model trainings, personalizations and custom re-rankers. 40 | ### [RFC Search Relevance](https://github.com/opensearch-project/search-processor/issues/1) 41 | 42 | ## Contributing 43 | 44 | See [developer guide](https://github.com/opensearch-project/opensearch-plugins/blob/main/BUILDING.md#developing-new-plugins-for-opensearch) and [how to contribute to this project](CONTRIBUTING.md). 45 | 46 | ## Getting Help 47 | 48 | If you find a bug, or have a feature request, please don't hesitate to open an issue in this repository. 49 | 50 | For more information, see [project website](https://opensearch.org/) and [documentation](https://opensearch.org/docs). If you need help and are unsure where to open an issue, try [forums](https://forum.opensearch.org/). 51 | 52 | ## Security 53 | 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public GitHub issue. 55 | 56 | ## License 57 | 58 | This project is licensed under the [Apache v2.0 License](LICENSE). 59 | 60 | ## Copyright 61 | 62 | Copyright OpenSearch Contributors. See [NOTICE](NOTICE) for details. 63 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Reporting a Vulnerability 2 | 3 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/) or directly via email to aws-security@amazon.com. Please do **not** create a public GitHub issue. -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/build.gradle: -------------------------------------------------------------------------------- 1 | import org.opensearch.gradle.test.RestIntegTestTask 2 | 3 | apply plugin: 'java' 4 | apply plugin: 'idea' 5 | apply plugin: 'opensearch.opensearchplugin' 6 | apply plugin: 'opensearch.yaml-rest-test' 7 | apply plugin: 'jacoco' 8 | 9 | group = 'org.opensearch' 10 | 11 | def pluginName = 'amazon-kendra-intelligent-ranking' 12 | def pluginDescription = 'Rerank search results using Amazon Kendra Intelligent Ranking' 13 | def projectPath = 'org.opensearch' 14 | def pathToPlugin = 'search.relevance' 15 | def pluginClassName = 'AmazonKendraIntelligentRankingPlugin' 16 | 17 | opensearchplugin { 18 | name "opensearch-${pluginName}-${plugin_version}.0" 19 | version "${plugin_version}" 20 | description pluginDescription 21 | classname "${projectPath}.${pathToPlugin}.${pluginClassName}" 22 | licenseFile rootProject.file('LICENSE') 23 | noticeFile rootProject.file('NOTICE') 24 | } 25 | 26 | java { 27 | targetCompatibility = JavaVersion.VERSION_21 28 | sourceCompatibility = JavaVersion.VERSION_21 29 | } 30 | 31 | // This requires an additional Jar not published as part of build-tools 32 | loggerUsageCheck.enabled = false 33 | 34 | // No need to validate pom, as we do not upload to maven/sonatype 35 | validateNebulaPom.enabled = false 36 | 37 | buildscript { 38 | repositories { 39 | mavenLocal() 40 | maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } 41 | mavenCentral() 42 | maven { url "https://plugins.gradle.org/m2/" } 43 | } 44 | 45 | dependencies { 46 | classpath "org.opensearch.gradle:build-tools:${opensearch_version}" 47 | } 48 | } 49 | 50 | repositories { 51 | mavenLocal() 52 | maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } 53 | mavenCentral() 54 | maven { url "https://plugins.gradle.org/m2/" } 55 | } 56 | 57 | dependencies { 58 | implementation 'com.ibm.icu:icu4j:57.2' 59 | implementation 'org.apache.httpcomponents:httpclient:4.5.14' 60 | implementation 'org.apache.httpcomponents:httpcore:4.4.16' 61 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.2' 62 | implementation 'com.fasterxml.jackson.core:jackson-core:2.18.2' 63 | implementation 'com.fasterxml.jackson.core:jackson-annotations:2.18.2' 64 | implementation 'commons-logging:commons-logging:1.2' 65 | implementation 'com.amazonaws:aws-java-sdk-sts:1.12.300' 66 | implementation 'com.amazonaws:aws-java-sdk-core:1.12.300' 67 | } 68 | 69 | 70 | allprojects { 71 | plugins.withId('jacoco') { 72 | jacoco.toolVersion = '0.8.9' 73 | } 74 | } 75 | 76 | 77 | test { 78 | include '**/*Tests.class' 79 | finalizedBy jacocoTestReport 80 | } 81 | 82 | task integTest(type: RestIntegTestTask) { 83 | description = "Run tests against a cluster" 84 | testClassesDirs = sourceSets.test.output.classesDirs 85 | classpath = sourceSets.test.runtimeClasspath 86 | } 87 | tasks.named("check").configure { dependsOn(integTest) } 88 | 89 | integTest { 90 | // The --debug-jvm command-line option makes the cluster debuggable; this makes the tests debuggable 91 | if (System.getProperty("test.debug") != null) { 92 | jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005' 93 | } 94 | } 95 | 96 | testClusters.integTest { 97 | testDistribution = "ARCHIVE" 98 | 99 | // This installs our plugin into the testClusters 100 | plugin(project.tasks.bundlePlugin.archiveFile) 101 | } 102 | 103 | run { 104 | useCluster testClusters.integTest 105 | } 106 | 107 | jacocoTestReport { 108 | dependsOn test 109 | reports { 110 | xml.required = true 111 | html.required = true 112 | } 113 | } 114 | 115 | // TODO: Enable these checks 116 | dependencyLicenses.enabled = false 117 | thirdPartyAudit.enabled = false 118 | loggerUsageCheck.enabled = false 119 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/AmazonKendraIntelligentRankingPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance; 9 | 10 | import org.opensearch.action.support.ActionFilter; 11 | import org.opensearch.client.Client; 12 | import org.opensearch.cluster.metadata.IndexNameExpressionResolver; 13 | import org.opensearch.cluster.service.ClusterService; 14 | import org.opensearch.common.settings.Setting; 15 | import org.opensearch.core.common.io.stream.NamedWriteableRegistry; 16 | import org.opensearch.core.xcontent.NamedXContentRegistry; 17 | import org.opensearch.env.Environment; 18 | import org.opensearch.env.NodeEnvironment; 19 | import org.opensearch.plugins.ActionPlugin; 20 | import org.opensearch.plugins.Plugin; 21 | import org.opensearch.plugins.SearchPipelinePlugin; 22 | import org.opensearch.plugins.SearchPlugin; 23 | import org.opensearch.repositories.RepositoriesService; 24 | import org.opensearch.script.ScriptService; 25 | import org.opensearch.search.pipeline.Processor; 26 | import org.opensearch.search.pipeline.SearchResponseProcessor; 27 | import org.opensearch.search.relevance.actionfilter.SearchActionFilter; 28 | import org.opensearch.search.relevance.client.OpenSearchClient; 29 | import org.opensearch.search.relevance.configuration.ResultTransformerConfigurationFactory; 30 | import org.opensearch.search.relevance.configuration.SearchConfigurationExtBuilder; 31 | import org.opensearch.search.relevance.transformer.ResultTransformer; 32 | import org.opensearch.search.relevance.transformer.kendraintelligentranking.KendraIntelligentRanker; 33 | import org.opensearch.search.relevance.transformer.kendraintelligentranking.client.KendraClientSettings; 34 | import org.opensearch.search.relevance.transformer.kendraintelligentranking.client.KendraHttpClient; 35 | import org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration.KendraIntelligentRankerSettings; 36 | import org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration.KendraIntelligentRankingConfigurationFactory; 37 | import org.opensearch.search.relevance.transformer.kendraintelligentranking.pipeline.KendraRankingResponseProcessor; 38 | import org.opensearch.threadpool.ThreadPool; 39 | import org.opensearch.watcher.ResourceWatcherService; 40 | 41 | import java.util.ArrayList; 42 | import java.util.Arrays; 43 | import java.util.Collection; 44 | import java.util.List; 45 | import java.util.Map; 46 | import java.util.function.Supplier; 47 | import java.util.stream.Collectors; 48 | 49 | public class AmazonKendraIntelligentRankingPlugin extends Plugin implements ActionPlugin, SearchPlugin, SearchPipelinePlugin { 50 | 51 | private OpenSearchClient openSearchClient; 52 | private KendraHttpClient kendraClient; 53 | private KendraIntelligentRanker kendraIntelligentRanker; 54 | private KendraClientSettings kendraClientSettings; 55 | 56 | private Collection getAllResultTransformers() { 57 | // Initialize and add other transformers here 58 | return List.of(this.kendraIntelligentRanker); 59 | } 60 | 61 | private Collection getResultTransformerConfigurationFactories() { 62 | return List.of(KendraIntelligentRankingConfigurationFactory.INSTANCE); 63 | } 64 | 65 | @Override 66 | public List getActionFilters() { 67 | return Arrays.asList(new SearchActionFilter(getAllResultTransformers(), openSearchClient)); 68 | } 69 | 70 | @Override 71 | public List> getSettings() { 72 | // NOTE: cannot use kendraIntelligentRanker.getTransformerSettings because the object is not yet created 73 | List> allTransformerSettings = new ArrayList<>(); 74 | allTransformerSettings.addAll(KendraIntelligentRankerSettings.getAllSettings()); 75 | // Add settings for other transformers here 76 | return allTransformerSettings; 77 | } 78 | 79 | @Override 80 | public Collection createComponents( 81 | Client client, 82 | ClusterService clusterService, 83 | ThreadPool threadPool, 84 | ResourceWatcherService resourceWatcherService, 85 | ScriptService scriptService, 86 | NamedXContentRegistry xContentRegistry, 87 | Environment environment, 88 | NodeEnvironment nodeEnvironment, 89 | NamedWriteableRegistry namedWriteableRegistry, 90 | IndexNameExpressionResolver indexNameExpressionResolver, 91 | Supplier repositoriesServiceSupplier 92 | ) { 93 | this.openSearchClient = new OpenSearchClient(client); 94 | this.kendraClientSettings = KendraClientSettings.getClientSettings(environment.settings()); 95 | this.kendraClient = new KendraHttpClient(this.kendraClientSettings); 96 | this.kendraIntelligentRanker = new KendraIntelligentRanker(this.kendraClient); 97 | 98 | return Arrays.asList( 99 | this.openSearchClient, 100 | this.kendraClientSettings, 101 | this.kendraClient, 102 | this.kendraIntelligentRanker 103 | ); 104 | } 105 | 106 | @Override 107 | public List> getSearchExts() { 108 | Map resultTransformerMap = getResultTransformerConfigurationFactories().stream() 109 | .collect(Collectors.toMap(ResultTransformerConfigurationFactory::getName, i -> i)); 110 | return List.of(new SearchExtSpec<>(SearchConfigurationExtBuilder.NAME, 111 | input -> new SearchConfigurationExtBuilder(input, resultTransformerMap), 112 | parser -> SearchConfigurationExtBuilder.parse(parser, resultTransformerMap))); 113 | } 114 | 115 | @Override 116 | public Map> getResponseProcessors(Parameters parameters) { 117 | return Map.of(KendraRankingResponseProcessor.TYPE, new KendraRankingResponseProcessor.Factory(this.kendraClientSettings)); 118 | } 119 | } -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/client/OpenSearchClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.client; 9 | 10 | import org.opensearch.action.admin.indices.settings.get.GetSettingsAction; 11 | import org.opensearch.action.admin.indices.settings.get.GetSettingsRequest; 12 | import org.opensearch.action.admin.indices.settings.get.GetSettingsResponse; 13 | import org.opensearch.client.Client; 14 | import org.opensearch.common.settings.Settings; 15 | import org.opensearch.core.action.ActionListener; 16 | 17 | public class OpenSearchClient { 18 | private final Client client; 19 | 20 | public OpenSearchClient(Client client) { 21 | this.client = client; 22 | } 23 | 24 | public void getIndexSettings(String indexName, String[] settingNames, ActionListener settingsListener) { 25 | GetSettingsRequest getSettingsRequest = new GetSettingsRequest() 26 | .indices(indexName); 27 | if (settingNames != null && settingNames.length > 0) { 28 | getSettingsRequest.names(settingNames); 29 | } 30 | ActionListener responseListener = ActionListener.map(settingsListener, r -> r.getIndexToSettings().get(indexName)); 31 | client.execute(GetSettingsAction.INSTANCE, getSettingsRequest, responseListener); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/configuration/ConfigurationUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.configuration; 9 | 10 | import org.opensearch.action.search.SearchRequest; 11 | import org.opensearch.common.settings.Settings; 12 | import org.opensearch.search.SearchExtBuilder; 13 | import org.opensearch.search.relevance.transformer.ResultTransformer; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Comparator; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.stream.Collectors; 20 | 21 | import static org.opensearch.search.relevance.configuration.Constants.RESULT_TRANSFORMER_SETTING_PREFIX; 22 | 23 | public class ConfigurationUtils { 24 | 25 | /** 26 | * Get result transformer configurations from Search Request 27 | * 28 | * @param settings all index settings configured for this plugin 29 | * @param resultTransformerMap map of transformed results 30 | * @return ordered and validated list of result transformers, empty list if not specified 31 | */ 32 | public static List getResultTransformersFromIndexConfiguration(Settings settings, 33 | Map resultTransformerMap) { 34 | List indexLevelConfigs = new ArrayList<>(); 35 | 36 | if (settings != null) { 37 | if (settings.getGroups(RESULT_TRANSFORMER_SETTING_PREFIX) != null) { 38 | for (Map.Entry transformerSettings : settings.getGroups(RESULT_TRANSFORMER_SETTING_PREFIX).entrySet()) { 39 | if (resultTransformerMap.containsKey(transformerSettings.getKey())) { 40 | ResultTransformer transformer = resultTransformerMap.get(transformerSettings.getKey()); 41 | indexLevelConfigs.add(transformer.getConfigurationFactory().configure(transformerSettings.getValue())); 42 | } 43 | } 44 | } 45 | } 46 | 47 | return reorderAndValidateConfigs(indexLevelConfigs); 48 | } 49 | 50 | /** 51 | * Get result transformer configurations from Search Request 52 | * 53 | * @param searchRequest input request 54 | * @return ordered and validated list of result transformers, empty list if not specified 55 | */ 56 | public static List getResultTransformersFromRequestConfiguration( 57 | final SearchRequest searchRequest) { 58 | 59 | // Fetch result transformers specified in request 60 | SearchConfigurationExtBuilder requestLevelSearchConfiguration = null; 61 | if (searchRequest.source() != null && searchRequest.source().ext() != null && !searchRequest.source().ext().isEmpty()) { 62 | // Filter ext builders by name 63 | List extBuilders = searchRequest.source().ext().stream() 64 | .filter(searchExtBuilder -> SearchConfigurationExtBuilder.NAME.equals(searchExtBuilder.getWriteableName())) 65 | .collect(Collectors.toList()); 66 | if (!extBuilders.isEmpty()) { 67 | requestLevelSearchConfiguration = (SearchConfigurationExtBuilder) extBuilders.get(0); 68 | } 69 | } 70 | 71 | List requestLevelConfigs = new ArrayList<>(); 72 | if (requestLevelSearchConfiguration != null) { 73 | requestLevelConfigs = reorderAndValidateConfigs(requestLevelSearchConfiguration.getResultTransformers()); 74 | } 75 | return requestLevelConfigs; 76 | } 77 | 78 | /** 79 | * Sort configurations in ascending order of invocation, and validate 80 | * 81 | * @param configs list of result transformer configurations 82 | * @return ordered and validated list of result transformers 83 | */ 84 | public static List reorderAndValidateConfigs( 85 | final List configs) throws IllegalArgumentException { 86 | 87 | // Sort 88 | configs.sort(Comparator.comparingInt(ResultTransformerConfiguration::getOrder)); 89 | 90 | for (int i = 0; i < configs.size(); ++i) { 91 | if (configs.get(i).getOrder() != (i + 1)) { 92 | throw new IllegalArgumentException("Expected order [" + (i + 1) + "] for transformer [" + 93 | configs.get(i).getTransformerName() + "], but found [" + configs.get(i).getOrder() + "]"); 94 | } 95 | } 96 | 97 | return configs; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/configuration/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.configuration; 9 | 10 | import static org.opensearch.search.relevance.transformer.TransformerType.RESULT_TRANSFORMER; 11 | 12 | public class Constants { 13 | public static final String PLUGIN_NAME = "searchrelevance"; 14 | public static final String SEARCH_CONFIGURATION = "search_configuration"; 15 | 16 | public static final String PLUGIN_SETTING_PREFIX = 17 | String.join(".", "index", "plugin", PLUGIN_NAME); 18 | public static final String RESULT_TRANSFORMER_SETTING_PREFIX = 19 | String.join(".", PLUGIN_SETTING_PREFIX, RESULT_TRANSFORMER.toString()); 20 | 21 | public static final String PROPERTIES = "properties"; 22 | public static final String ORDER = "order"; 23 | } 24 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/configuration/ResultTransformerConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.configuration; 9 | 10 | public abstract class ResultTransformerConfiguration extends TransformerConfiguration { 11 | 12 | public abstract String getTransformerName(); 13 | } 14 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/configuration/ResultTransformerConfigurationFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.configuration; 9 | 10 | import org.opensearch.core.common.io.stream.StreamInput; 11 | import org.opensearch.common.settings.Settings; 12 | import org.opensearch.core.xcontent.XContentParser; 13 | 14 | import java.io.IOException; 15 | 16 | public interface ResultTransformerConfigurationFactory { 17 | String getName(); 18 | 19 | /** 20 | * Build configuration based on index settings 21 | * @param indexSettings a set of index settings under a group scoped based on this result transformer's name. 22 | * @return a transformer configuration based on the passed settings. 23 | */ 24 | ResultTransformerConfiguration configure(Settings indexSettings); 25 | 26 | /** 27 | * Build configuration from serialized XContent, e.g. as part of a serialized {@link SearchConfigurationExtBuilder}. 28 | * @param parser an XContentParser pointing to a node serialized from a {@link ResultTransformerConfiguration} of 29 | * this type. 30 | * @return a transformer configuration based on the parameters specified in the XContent. 31 | */ 32 | ResultTransformerConfiguration configure(XContentParser parser) throws IOException; 33 | 34 | /** 35 | * Build configuration from a serialized stream. 36 | * @param streamInput a {@link org.opensearch.core.common.io.stream.Writeable} serialized representation of transformer 37 | * configuration. 38 | * @return configuration the deserialized transformer configuration. 39 | */ 40 | ResultTransformerConfiguration configure(StreamInput streamInput) throws IOException; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/configuration/TransformerConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.configuration; 9 | 10 | import static org.opensearch.search.relevance.configuration.Constants.ORDER; 11 | import static org.opensearch.search.relevance.configuration.Constants.PROPERTIES; 12 | 13 | import org.opensearch.core.common.io.stream.Writeable; 14 | import org.opensearch.core.ParseField; 15 | import org.opensearch.core.xcontent.ToXContentObject; 16 | 17 | public abstract class TransformerConfiguration implements Writeable, ToXContentObject { 18 | protected static final ParseField TRANSFORMER_ORDER = new ParseField(ORDER); 19 | protected static final ParseField TRANSFORMER_PROPERTIES = new ParseField(PROPERTIES); 20 | 21 | protected int order; 22 | 23 | public int getOrder() { 24 | return this.order; 25 | } 26 | 27 | public void setOrder(final int order) { 28 | this.order = order; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/ResultTransformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer; 9 | 10 | import org.opensearch.action.search.SearchRequest; 11 | import org.opensearch.common.settings.Setting; 12 | import org.opensearch.search.SearchHits; 13 | import org.opensearch.search.relevance.configuration.ResultTransformerConfiguration; 14 | import org.opensearch.search.relevance.configuration.ResultTransformerConfigurationFactory; 15 | 16 | import java.util.List; 17 | 18 | public interface ResultTransformer { 19 | /** 20 | * Get the list of settings supported by the transformer 21 | * @return list of transformer settings 22 | */ 23 | List> getTransformerSettings(); 24 | 25 | /** 26 | * @return a factory able to construct configurations for this transformer. 27 | */ 28 | ResultTransformerConfigurationFactory getConfigurationFactory(); 29 | 30 | /** 31 | * Decide whether to apply the transformer on the input request 32 | * @param request input Search Request 33 | * @param configuration Configuration parameters for the transformer 34 | * @return boolean decision on whether to apply the transformer 35 | */ 36 | boolean shouldTransform(final SearchRequest request, final ResultTransformerConfiguration configuration); 37 | 38 | /** 39 | * Preprocess the incoming Search Request to support the requirements of the transformer 40 | * @param request input Search Request 41 | * @param configuration Configuration parameters for the transformer 42 | * @return SearchRequest with updated attributes 43 | */ 44 | SearchRequest preprocessRequest(final SearchRequest request, 45 | final ResultTransformerConfiguration configuration); 46 | 47 | /** 48 | * Rank hits based on the provided query 49 | * @param hits hits to be re-ranked 50 | * @param request Search request 51 | * @param configuration Configuration parameters for the transformer 52 | * @return SearchHits ordered by score generated by ranker 53 | */ 54 | SearchHits transform(final SearchHits hits, 55 | final SearchRequest request, 56 | final ResultTransformerConfiguration configuration); 57 | } 58 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/TransformerType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer; 9 | 10 | public enum TransformerType { 11 | RESULT_TRANSFORMER("result_transformer"); 12 | 13 | private final String type; 14 | 15 | TransformerType(String type) { 16 | this.type = type; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return type; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/client/KendraClientSettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.client; 9 | 10 | import static org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration.KendraIntelligentRankerSettings.ACCESS_KEY_SETTING; 11 | import static org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration.KendraIntelligentRankerSettings.ASSUME_ROLE_ARN_SETTING; 12 | import static org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration.KendraIntelligentRankerSettings.EXECUTION_PLAN_ID_SETTING; 13 | import static org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration.KendraIntelligentRankerSettings.SECRET_KEY_SETTING; 14 | import static org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration.KendraIntelligentRankerSettings.SERVICE_ENDPOINT_SETTING; 15 | import static org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration.KendraIntelligentRankerSettings.SERVICE_REGION_SETTING; 16 | import static org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration.KendraIntelligentRankerSettings.SESSION_TOKEN_SETTING; 17 | 18 | import com.amazonaws.auth.AWSCredentials; 19 | import com.amazonaws.auth.BasicAWSCredentials; 20 | import com.amazonaws.auth.BasicSessionCredentials; 21 | 22 | import org.apache.logging.log4j.LogManager; 23 | import org.apache.logging.log4j.Logger; 24 | import org.opensearch.core.common.settings.SecureString; 25 | import org.opensearch.common.settings.Settings; 26 | import org.opensearch.common.settings.SettingsException; 27 | 28 | /** 29 | * A container for settings used to create a Kendra client. 30 | */ 31 | public final class KendraClientSettings { 32 | 33 | private static final Logger logger = LogManager.getLogger(KendraClientSettings.class); 34 | 35 | /** 36 | * Credentials to authenticate with Kendra. 37 | */ 38 | private final AWSCredentials credentials; 39 | private final String serviceEndpoint; 40 | private final String serviceRegion; 41 | private final String executionPlanId; 42 | private final String assumeRoleArn; 43 | 44 | protected KendraClientSettings(AWSCredentials credentials, String serviceEndpoint, String serviceRegion, String executionPlanId, String assumeRoleArn) { 45 | this.credentials = credentials; 46 | this.serviceEndpoint = serviceEndpoint; 47 | this.serviceRegion = serviceRegion; 48 | this.executionPlanId = executionPlanId; 49 | this.assumeRoleArn = assumeRoleArn; 50 | } 51 | 52 | public AWSCredentials getCredentials() { 53 | return credentials; 54 | } 55 | 56 | public String getExecutionPlanId() { 57 | return executionPlanId; 58 | } 59 | 60 | public String getServiceEndpoint() { 61 | return serviceEndpoint; 62 | } 63 | 64 | public String getServiceRegion() { 65 | return serviceRegion; 66 | } 67 | 68 | public String getAssumeRoleArn() { 69 | return assumeRoleArn; 70 | } 71 | 72 | static AWSCredentials loadCredentials(Settings settings) { 73 | try (SecureString key = ACCESS_KEY_SETTING.get(settings); 74 | SecureString secret = SECRET_KEY_SETTING.get(settings); 75 | SecureString sessionToken = SESSION_TOKEN_SETTING.get(settings)) { 76 | if (key.length() == 0 && secret.length() == 0) { 77 | if (sessionToken.length() > 0) { 78 | throw new SettingsException("Setting [{}] is set but [{}] and [{}] are not", 79 | SESSION_TOKEN_SETTING.getKey(), ACCESS_KEY_SETTING.getKey(), SECRET_KEY_SETTING.getKey()); 80 | } 81 | 82 | logger.debug("Using either environment variables, system properties or instance profile credentials"); 83 | return null; 84 | } else if (key.length() == 0 || secret.length() == 0) { 85 | throw new SettingsException("One of settings [{}] and [{}] is not set.", 86 | ACCESS_KEY_SETTING.getKey(), SECRET_KEY_SETTING.getKey()); 87 | } else { 88 | final AWSCredentials credentials; 89 | if (sessionToken.length() == 0) { 90 | logger.debug("Using basic key/secret credentials"); 91 | credentials = new BasicAWSCredentials(key.toString(), secret.toString()); 92 | } else { 93 | logger.debug("Using basic session credentials"); 94 | credentials = new BasicSessionCredentials(key.toString(), secret.toString(), sessionToken.toString()); 95 | } 96 | return credentials; 97 | } 98 | } 99 | } 100 | 101 | /** 102 | * Parse settings for a single client. 103 | * @param settings a {@link Settings} instance from which to derive the endpoint settings 104 | * @return KendraClientSettings comprising credentials and endpoint settings 105 | */ 106 | public static KendraClientSettings getClientSettings(Settings settings) { 107 | final AWSCredentials credentials = loadCredentials(settings); 108 | return new KendraClientSettings( 109 | credentials, 110 | SERVICE_ENDPOINT_SETTING.get(settings), 111 | SERVICE_REGION_SETTING.get(settings), 112 | EXECUTION_PLAN_ID_SETTING.get(settings), 113 | ASSUME_ROLE_ARN_SETTING.get(settings) 114 | ); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/client/SimpleAwsErrorHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.client; 9 | 10 | import com.amazonaws.AmazonServiceException; 11 | import com.amazonaws.http.HttpResponse; 12 | import com.amazonaws.http.HttpResponseHandler; 13 | 14 | import java.nio.charset.StandardCharsets; 15 | 16 | public class SimpleAwsErrorHandler implements HttpResponseHandler { 17 | @Override public AmazonServiceException handle(HttpResponse response) throws Exception { 18 | AmazonServiceException ase = new AmazonServiceException(new String(response.getContent().readAllBytes(), StandardCharsets.UTF_8)); 19 | ase.setStatusCode(response.getStatusCode()); 20 | ase.setServiceName(response.getRequest().getServiceName()); 21 | ase.setErrorCode(response.getStatusText()); 22 | return ase; 23 | } 24 | 25 | @Override public boolean needsConnectionLeftOpen() { 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/client/SimpleResponseHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.client; 9 | 10 | import com.amazonaws.http.HttpResponse; 11 | import com.amazonaws.http.HttpResponseHandler; 12 | 13 | import java.nio.charset.StandardCharsets; 14 | 15 | public class SimpleResponseHandler implements HttpResponseHandler { 16 | @Override public String handle(HttpResponse response) throws Exception { 17 | return new String(response.getContent().readAllBytes(), StandardCharsets.UTF_8); 18 | } 19 | 20 | @Override public boolean needsConnectionLeftOpen() { 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/configuration/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration; 9 | 10 | import static org.opensearch.search.relevance.configuration.Constants.ORDER; 11 | import static org.opensearch.search.relevance.configuration.Constants.PROPERTIES; 12 | import static org.opensearch.search.relevance.configuration.Constants.RESULT_TRANSFORMER_SETTING_PREFIX; 13 | 14 | public class Constants { 15 | // Transformer name 16 | public static final String KENDRA_INTELLIGENT_RANKING = "kendra_intelligent_ranking"; 17 | // Transformer properties 18 | public static final String BODY_FIELD = "body_field"; 19 | public static final String TITLE_FIELD = "title_field"; 20 | public static final String DOC_LIMIT = "doc_limit"; 21 | 22 | public static final String KENDRA_SETTINGS_PREFIX = 23 | String.join(".", RESULT_TRANSFORMER_SETTING_PREFIX, KENDRA_INTELLIGENT_RANKING); 24 | 25 | public static final String ORDER_SETTING_NAME = 26 | String.join(".", KENDRA_SETTINGS_PREFIX, ORDER); 27 | public static final String BODY_FIELD_SETTING_NAME = 28 | String.join(".", KENDRA_SETTINGS_PREFIX, PROPERTIES, BODY_FIELD); 29 | public static final String TITLE_FIELD_SETTING_NAME = 30 | String.join(".", KENDRA_SETTINGS_PREFIX, PROPERTIES, TITLE_FIELD); 31 | public static final String DOC_LIMIT_SETTING_NAME = 32 | String.join(".", KENDRA_SETTINGS_PREFIX, PROPERTIES, DOC_LIMIT); 33 | 34 | public static final int KENDRA_DEFAULT_DOC_LIMIT = 25; 35 | } 36 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/configuration/KendraIntelligentRankerSettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration; 9 | 10 | import java.util.Arrays; 11 | import java.util.Collections; 12 | import java.util.List; 13 | import java.util.function.Function; 14 | import org.opensearch.common.settings.SecureSetting; 15 | import org.opensearch.core.common.settings.SecureString; 16 | import org.opensearch.common.settings.Setting; 17 | import org.opensearch.common.settings.Setting.Property; 18 | 19 | public class KendraIntelligentRankerSettings { 20 | 21 | /** 22 | * Flag controlling whether to invoke Kendra for rescoring. 23 | */ 24 | public static final Setting KENDRA_ORDER_SETTING = Setting.intSetting(Constants.ORDER_SETTING_NAME, 1, 1, 25 | Property.Dynamic, Property.IndexScope); 26 | 27 | /** 28 | * Validator for body and title field settings 29 | */ 30 | static final class FieldValidator implements Setting.Validator> { 31 | 32 | private final String settingName; 33 | 34 | public FieldValidator(final String name) { 35 | this.settingName = name; 36 | } 37 | 38 | @Override 39 | public void validate(List value) { 40 | if (value != null && value.size() > 1) { 41 | throw new IllegalArgumentException("[" + this.settingName + "] can have at most 1 element"); 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * Validator for doc limit setting 48 | */ 49 | static final class DocLimitValidator implements Setting.Validator { 50 | 51 | private final String settingName; 52 | 53 | public DocLimitValidator(final String name) { 54 | this.settingName = name; 55 | } 56 | 57 | @Override 58 | public void validate(Integer value) { 59 | if (value != null && value < Constants.KENDRA_DEFAULT_DOC_LIMIT) { 60 | throw new IllegalArgumentException("Setting the value of [" + this.settingName + "] below " 61 | + Constants.KENDRA_DEFAULT_DOC_LIMIT + " will affect ranking accuracy"); 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * Validator objects 68 | */ 69 | public static final FieldValidator BODY_FIELD_VALIDATOR = new FieldValidator(Constants.BODY_FIELD); 70 | public static final FieldValidator TITLE_FIELD_VALIDATOR = new FieldValidator(Constants.TITLE_FIELD); 71 | public static final DocLimitValidator DOC_LIMIT_VALIDATOR = new DocLimitValidator(Constants.DOC_LIMIT); 72 | 73 | /** 74 | * Document field to be considered as "body" when invoking Kendra. 75 | */ 76 | public static final Setting> KENDRA_BODY_FIELD_SETTING = Setting.listSetting(Constants.BODY_FIELD_SETTING_NAME, 77 | Collections.emptyList(), Function.identity(), BODY_FIELD_VALIDATOR, 78 | Property.Dynamic, Property.IndexScope); 79 | 80 | /** 81 | * Document field to be considered as "title" when invoking Kendra. 82 | */ 83 | public static final Setting> KENDRA_TITLE_FIELD_SETTING = Setting.listSetting(Constants.TITLE_FIELD_SETTING_NAME, 84 | Collections.emptyList(), Function.identity(), TITLE_FIELD_VALIDATOR, 85 | Property.Dynamic, Property.IndexScope); 86 | 87 | 88 | 89 | public static final Setting KENDRA_DOC_LIMIT_SETTING = Setting.intSetting( 90 | Constants.DOC_LIMIT_SETTING_NAME, Constants.KENDRA_DEFAULT_DOC_LIMIT, 1, 91 | DOC_LIMIT_VALIDATOR, Property.Dynamic, Property.IndexScope); 92 | 93 | 94 | 95 | /** 96 | * The access key (ie login id) for connecting to Kendra. 97 | */ 98 | public static final Setting ACCESS_KEY_SETTING = SecureSetting.secureString("kendra_intelligent_ranking.aws.access_key", null); 99 | 100 | /** 101 | * The secret key (ie password) for connecting to Kendra. 102 | */ 103 | public static final Setting SECRET_KEY_SETTING = SecureSetting.secureString("kendra_intelligent_ranking.aws.secret_key", null); 104 | 105 | /** 106 | * The session token for connecting to Kendra. 107 | */ 108 | public static final Setting SESSION_TOKEN_SETTING = SecureSetting.secureString("kendra_intelligent_ranking.aws.session_token", null); 109 | 110 | public static final Setting SERVICE_ENDPOINT_SETTING = Setting.simpleString("kendra_intelligent_ranking.service.endpoint", Setting.Property.NodeScope); 111 | 112 | public static final Setting SERVICE_REGION_SETTING = Setting.simpleString("kendra_intelligent_ranking.service.region", Setting.Property.NodeScope); 113 | 114 | public static final Setting EXECUTION_PLAN_ID_SETTING = Setting.simpleString("kendra_intelligent_ranking.service.execution_plan_id", Setting.Property.NodeScope); 115 | 116 | public static final Setting ASSUME_ROLE_ARN_SETTING = Setting.simpleString("kendra_intelligent_ranking.service.assume_role_arn", Setting.Property.NodeScope); 117 | 118 | public static List> getAllSettings() { 119 | return Arrays.asList( 120 | KENDRA_ORDER_SETTING, 121 | KENDRA_BODY_FIELD_SETTING, 122 | KENDRA_TITLE_FIELD_SETTING, 123 | KENDRA_DOC_LIMIT_SETTING, 124 | ACCESS_KEY_SETTING, 125 | SECRET_KEY_SETTING, 126 | SESSION_TOKEN_SETTING, 127 | SERVICE_ENDPOINT_SETTING, 128 | SERVICE_REGION_SETTING, 129 | EXECUTION_PLAN_ID_SETTING, 130 | ASSUME_ROLE_ARN_SETTING 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/configuration/KendraIntelligentRankingConfigurationFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration; 9 | 10 | import org.opensearch.core.common.io.stream.StreamInput; 11 | import org.opensearch.common.settings.Settings; 12 | import org.opensearch.core.xcontent.XContentParser; 13 | import org.opensearch.search.relevance.configuration.ResultTransformerConfiguration; 14 | import org.opensearch.search.relevance.configuration.ResultTransformerConfigurationFactory; 15 | import org.opensearch.search.relevance.transformer.kendraintelligentranking.KendraIntelligentRanker; 16 | 17 | import java.io.IOException; 18 | 19 | public class KendraIntelligentRankingConfigurationFactory implements ResultTransformerConfigurationFactory { 20 | private KendraIntelligentRankingConfigurationFactory() { 21 | } 22 | 23 | public static final KendraIntelligentRankingConfigurationFactory INSTANCE = 24 | new KendraIntelligentRankingConfigurationFactory(); 25 | 26 | @Override 27 | public String getName() { 28 | return KendraIntelligentRanker.NAME; 29 | } 30 | 31 | @Override 32 | public ResultTransformerConfiguration configure(Settings indexSettings) { 33 | return new KendraIntelligentRankingConfiguration(indexSettings); 34 | } 35 | 36 | @Override 37 | public ResultTransformerConfiguration configure(XContentParser parser) throws IOException { 38 | return KendraIntelligentRankingConfiguration.parse(parser); 39 | } 40 | 41 | @Override 42 | public ResultTransformerConfiguration configure(StreamInput streamInput) throws IOException { 43 | return new KendraIntelligentRankingConfiguration(streamInput); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/model/KendraIntelligentRankingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.model; 9 | 10 | import java.io.IOException; 11 | import org.opensearch.OpenSearchException; 12 | import org.opensearch.core.common.io.stream.StreamInput; 13 | 14 | public class KendraIntelligentRankingException extends OpenSearchException { 15 | public KendraIntelligentRankingException(StreamInput in) throws IOException { 16 | super(in); 17 | } 18 | 19 | public KendraIntelligentRankingException(String message) { 20 | super(message); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/model/PassageScore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.model; 9 | 10 | public class PassageScore { 11 | private double score; 12 | private int index; 13 | 14 | public PassageScore(double score, int index) { 15 | this.score = score; 16 | this.index = index; 17 | } 18 | 19 | public double getScore() { 20 | return score; 21 | } 22 | 23 | public int getIndex() { 24 | return index; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/model/dto/Document.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.model.dto; 9 | 10 | import com.fasterxml.jackson.databind.PropertyNamingStrategies; 11 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 12 | 13 | import java.util.List; 14 | import java.util.Objects; 15 | 16 | @JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class) 17 | public class Document { 18 | 19 | private String id; 20 | private String groupId; 21 | private List tokenizedTitle; 22 | private List tokenizedBody; 23 | private Float originalScore; 24 | 25 | public Document(String id, String groupId, List tokenizedTitle, List tokenizedBody, 26 | Float originalScore) { 27 | this.id = id; 28 | this.groupId = groupId; 29 | this.tokenizedTitle = tokenizedTitle; 30 | this.tokenizedBody = tokenizedBody; 31 | this.originalScore = originalScore; 32 | } 33 | 34 | /** 35 | * No-args constructor used to deserialize. 36 | */ 37 | public Document() { 38 | } 39 | 40 | public String getId() { 41 | return id; 42 | } 43 | 44 | public void setId(String id) { 45 | this.id = id; 46 | } 47 | 48 | public String getGroupId() { 49 | return groupId; 50 | } 51 | 52 | public void setGroupId(String groupId) { 53 | this.groupId = groupId; 54 | } 55 | 56 | public List getTokenizedTitle() { 57 | return tokenizedTitle; 58 | } 59 | 60 | public void setTokenizedTitle(List tokenizedTitle) { 61 | this.tokenizedTitle = tokenizedTitle; 62 | } 63 | 64 | public List getTokenizedBody() { 65 | return tokenizedBody; 66 | } 67 | 68 | public void setTokenizedBody(List tokenizedBody) { 69 | this.tokenizedBody = tokenizedBody; 70 | } 71 | 72 | public Float getOriginalScore() { 73 | return originalScore; 74 | } 75 | 76 | public void setOriginalScore(Float originalScore) { 77 | this.originalScore = originalScore; 78 | } 79 | 80 | @Override 81 | public boolean equals(Object o) { 82 | if (this == o) return true; 83 | if (o == null || getClass() != o.getClass()) return false; 84 | Document document = (Document) o; 85 | return Objects.equals(id, document.id) && Objects.equals(groupId, document.groupId) && Objects.equals(tokenizedTitle, document.tokenizedTitle) && Objects.equals(tokenizedBody, document.tokenizedBody) && Objects.equals(originalScore, document.originalScore); 86 | } 87 | 88 | @Override 89 | public int hashCode() { 90 | return Objects.hash(id, groupId, tokenizedTitle, tokenizedBody, originalScore); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/model/dto/RescoreRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.model.dto; 9 | 10 | import com.fasterxml.jackson.databind.PropertyNamingStrategies; 11 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 12 | 13 | import java.util.List; 14 | 15 | @JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class) 16 | public class RescoreRequest { 17 | private String searchQuery; 18 | private List documents; 19 | 20 | public RescoreRequest(String searchQuery, List documents) { 21 | this.searchQuery = searchQuery; 22 | this.documents = documents; 23 | } 24 | 25 | public String getSearchQuery() { 26 | return searchQuery; 27 | } 28 | 29 | public void setSearchQuery(String searchQuery) { 30 | this.searchQuery = searchQuery; 31 | } 32 | 33 | public List getDocuments() { 34 | return documents; 35 | } 36 | 37 | public void setDocuments(List documents) { 38 | this.documents = documents; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/model/dto/RescoreResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.model.dto; 9 | 10 | import com.fasterxml.jackson.databind.PropertyNamingStrategies; 11 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 12 | 13 | import java.util.List; 14 | 15 | @JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class) 16 | public class RescoreResult { 17 | private String rescoreId; 18 | private List resultItems; 19 | 20 | public String getRescoreId() { 21 | return rescoreId; 22 | } 23 | 24 | public List getResultItems() { 25 | return resultItems; 26 | } 27 | 28 | /** 29 | * Setter used for unit tests. 30 | * @param rescoreId The identifier associated with the scores that Amazon Kendra Intelligent Ranking 31 | * gives to the results. 32 | */ 33 | public void setRescoreId(String rescoreId) { 34 | this.rescoreId = rescoreId; 35 | } 36 | 37 | /** 38 | * Setter used for unit tests. 39 | * @param resultItems A list of result items for documents with new relevancy scores. 40 | */ 41 | public void setResultItems(List resultItems) { 42 | this.resultItems = resultItems; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/model/dto/RescoreResultItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.model.dto; 9 | 10 | import com.fasterxml.jackson.databind.PropertyNamingStrategies; 11 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 12 | 13 | @JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class) 14 | public class RescoreResultItem { 15 | private String documentId; 16 | private Float score; 17 | 18 | public String getDocumentId() { 19 | return documentId; 20 | } 21 | 22 | public Float getScore() { 23 | return score; 24 | } 25 | 26 | /** 27 | * Setter for unit tests. 28 | * @param documentId the ID of the rescored document. 29 | */ 30 | public void setDocumentId(String documentId) { 31 | this.documentId = documentId; 32 | } 33 | 34 | /** 35 | * Setter for unit tests. 36 | * @param score the updated score of the document. 37 | */ 38 | public void setScore(Float score) { 39 | this.score = score; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/preprocess/BM25Scorer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.preprocess; 9 | 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | public class BM25Scorer { 17 | private Map wordToDocumentCount = new HashMap<>(); 18 | private double b; 19 | private double k1; 20 | private int totalNumberOfDocs; 21 | private double averageDocumentLength; // avdl 22 | 23 | /** 24 | * Initialize dataset. 25 | * 26 | * @param b free parameter for BM25 27 | * @param k1 free parameter for BM25 28 | * @param documents list of documents, each document is represented by a list of words 29 | */ 30 | public BM25Scorer(double b, double k1, List> documents) { 31 | this.b = b; 32 | this.k1 = k1; 33 | this.totalNumberOfDocs = documents.size(); 34 | 35 | double totalDocumentLength = 0; 36 | for (List document : documents) { 37 | totalDocumentLength += document.size(); 38 | 39 | Set uniqueWordsInDocument = new HashSet<>(document); // add to set to remove duplicates 40 | for (String term : uniqueWordsInDocument) { 41 | wordToDocumentCount.put(term, wordToDocumentCount.getOrDefault(term, 0) + 1); 42 | } 43 | } 44 | this.averageDocumentLength = totalDocumentLength / documents.size(); 45 | } 46 | 47 | /** 48 | * Calculate the BM25 score of a document given a query. 49 | * 50 | * @param query query represented as a list of words 51 | * @param document document represented as a list of words 52 | * @return the BM25 score 53 | */ 54 | public double score(List query, List document) { 55 | double score = 0; 56 | 57 | Map documentWordCounts = new HashMap<>(); 58 | for (String word : document) { 59 | documentWordCounts.put(word, documentWordCounts.getOrDefault(word, 0) + 1); 60 | } 61 | 62 | for (String queryWord : query) { 63 | if (!documentWordCounts.containsKey(queryWord)) { 64 | continue; 65 | } 66 | double termFrequency = (double) documentWordCounts.get(queryWord) / document.size(); 67 | double denominator = termFrequency + k1 * (1 - b + b * document.size() / averageDocumentLength); 68 | double idf = idf(queryWord); 69 | double numerator = idf * termFrequency * (k1 + 1); 70 | score += numerator / denominator; 71 | } 72 | return score; 73 | } 74 | 75 | /** 76 | * Calculate the idf (inverse document frequency) of a word in the dataset. 77 | * 78 | * @param word word to calculate idf on 79 | * @return idf value 80 | */ 81 | private double idf(String word) { 82 | return totalNumberOfDocs > 0 ? Math.log10((double) totalNumberOfDocs / wordToDocumentCount.get(word)) : 0; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/preprocess/SentenceSplitter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.preprocess; 9 | 10 | import com.ibm.icu.text.BreakIterator; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Locale; 16 | import java.util.Set; 17 | import java.util.regex.Pattern; 18 | import java.util.stream.Collectors; 19 | 20 | public class SentenceSplitter { 21 | 22 | /** 23 | * Split the input text into sentences 24 | * @param text input text 25 | * @return list of strings, each a sentence 26 | */ 27 | public List split(final String text) { 28 | if (text == null) { 29 | return new ArrayList<>(); 30 | } 31 | 32 | final BreakIterator breakIterator = BreakIterator.getSentenceInstance(Locale.ENGLISH); 33 | breakIterator.setText(text); 34 | 35 | List sentences = new ArrayList(); 36 | int start = breakIterator.first(); 37 | String currentSentence; 38 | 39 | for (int end = breakIterator.next(); end != BreakIterator.DONE; start = end, end = breakIterator.next()) { 40 | currentSentence = text.substring(start, end).stripTrailing(); 41 | if (!currentSentence.isEmpty()) { 42 | sentences.add(currentSentence); 43 | } 44 | } 45 | return sentences; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/preprocess/TextTokenizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.preprocess; 9 | 10 | import com.ibm.icu.text.BreakIterator; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Locale; 16 | import java.util.Set; 17 | import java.util.regex.Matcher; 18 | import java.util.regex.Pattern; 19 | import java.util.stream.Collectors; 20 | 21 | public class TextTokenizer { 22 | private static final int MINIMUM_WORD_LENGTH = 2; 23 | private static final int MAXIMUM_WORD_LENGTH = 25; 24 | private static final Set STOP_WORDS = new HashSet<>( 25 | Arrays.asList("i", "me", "my", "myself", "we", "our", "ours", "ourselves", "you", "your", "yours", "yourself", "yourselves", "he", "him", "his", 26 | "himself", "she", "her", "hers", "herself", "it", "its", "itself", "they", "them", "their", "theirs", "themselves", "what", "which", "who", 27 | "whom", "this", "that", "these", "those", "am", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "having", "do", 28 | "does", "did", "doing", "a", "an", "the", "and", "but", "if", "or", "because", "as", "until", "while", "of", "at", "by", "for", "with", 29 | "about", "against", "between", "into", "through", "during", "before", "after", "above", "below", "to", "from", "up", "down", "in", "out", 30 | "on", "off", "over", "under", "again", "further", "then", "once", "here", "there", "when", "where", "why", "how", "all", "any", "both", 31 | "each", "few", "more", "most", "other", "some", "such", "no", "nor", "not", "only", "own", "same", "so", "than", "too", "very", "s", "t", 32 | "can", "will", "just", "don", "should", "now")); 33 | private static final Pattern ALL_PUNCTUATIONS_REGEX = Pattern.compile("^\\p{Pc}+$|^\\p{Pd}+$|^\\p{Pe}+$|^\\p{Pf}+$|^\\p{Pi}+$|^\\p{Po}+$|^\\p{Ps}+$"); 34 | private static final Pattern PUNCTUATIONS_REGEX_PATTERN = Pattern.compile("\\p{Pc}|\\p{Pd}|\\p{Pe}|\\p{Pf}|\\p{Pi}|\\p{Po}|\\p{Ps}"); 35 | 36 | public List> tokenize(List texts) { 37 | if (texts == null) { 38 | return new ArrayList<>(); 39 | } 40 | 41 | return texts.stream() 42 | .map(text -> tokenize(text)) 43 | .collect(Collectors.toList()); 44 | } 45 | 46 | /** 47 | * Split the input text into tokens, with post-processing to remove stop words, punctuation, etc. 48 | * @param text input text 49 | * @return list of tokens 50 | */ 51 | public List tokenize(String text) { 52 | if (text == null) { 53 | return new ArrayList<>(); 54 | } 55 | 56 | final BreakIterator breakIterator = BreakIterator.getWordInstance(Locale.ENGLISH); 57 | breakIterator.setText(text); 58 | 59 | List tokens = new ArrayList(); 60 | int start = breakIterator.first(); 61 | String currentWord; 62 | for (int end = breakIterator.next(); end != BreakIterator.DONE; start = end, end = breakIterator.next()) { 63 | currentWord = text.substring(start, end).stripTrailing().toLowerCase(Locale.ENGLISH); 64 | if (currentWord.isEmpty()) { 65 | continue; 66 | } 67 | // Split long words 68 | List shortenedTokens = new ArrayList<>(); 69 | if (currentWord.length() <= MAXIMUM_WORD_LENGTH) { 70 | shortenedTokens.add(currentWord); 71 | } else { 72 | for (int i = 0; i < currentWord.length(); i += MAXIMUM_WORD_LENGTH) { 73 | shortenedTokens.add(currentWord.substring(i, Math.min(currentWord.length(), i + MAXIMUM_WORD_LENGTH))); 74 | } 75 | } 76 | // Filter out punctuation, short words, numbers 77 | for (String shortenedToken : shortenedTokens) { 78 | if (!isWordAllPunctuation(shortenedToken) && !STOP_WORDS.contains(shortenedToken) && 79 | shortenedToken.length() >= MINIMUM_WORD_LENGTH && !isNumeric(shortenedToken)) { 80 | String tokenWithInWordPunctuationRemoved = removeInWordPunctuation(shortenedToken); 81 | if (!tokenWithInWordPunctuationRemoved.isEmpty()) { 82 | tokens.add(tokenWithInWordPunctuationRemoved); 83 | } 84 | } 85 | } 86 | } 87 | return tokens; 88 | } 89 | 90 | boolean isWordAllPunctuation(final String token) { 91 | return (token != null) && ALL_PUNCTUATIONS_REGEX.matcher(token).matches(); 92 | } 93 | 94 | boolean isNumeric(final String token) { 95 | if (token == null) { 96 | return false; 97 | } 98 | try { 99 | Double.parseDouble(token); 100 | } catch (NumberFormatException e) { 101 | return false; 102 | } 103 | return true; 104 | } 105 | 106 | String removeInWordPunctuation(String token) { 107 | if (token == null) { 108 | return null; 109 | } 110 | return PUNCTUATIONS_REGEX_PATTERN.matcher(token).replaceAll(""); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/main/plugin-metadata/plugin-security.policy: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | 9 | grant { 10 | permission java.lang.RuntimePermission "accessDeclaredMembers"; 11 | permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; 12 | 13 | permission java.net.SocketPermission "*", "connect,resolve"; 14 | permission java.lang.RuntimePermission "getClassLoader"; 15 | }; 16 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/AmazonKendraIntelligentRankingPluginIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance; 9 | 10 | import org.apache.hc.core5.http.ParseException; 11 | import org.apache.hc.core5.http.io.entity.EntityUtils; 12 | import org.opensearch.client.Request; 13 | import org.opensearch.client.Response; 14 | import org.opensearch.test.rest.OpenSearchRestTestCase; 15 | 16 | import java.io.IOException; 17 | 18 | public class AmazonKendraIntelligentRankingPluginIT extends OpenSearchRestTestCase { 19 | 20 | public void testPluginInstalled() throws IOException, ParseException { 21 | Response response = client().performRequest(new Request("GET", "/_cat/plugins")); 22 | String body = EntityUtils.toString(response.getEntity()); 23 | 24 | logger.info("response body: {}", body); 25 | assertNotNull(body); 26 | assertTrue(body.contains("amazon-kendra-intelligent-ranking")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/SearchRelevanceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance; 9 | 10 | import org.opensearch.test.OpenSearchTestCase; 11 | 12 | public class SearchRelevanceTests extends OpenSearchTestCase { 13 | // Add unit tests for your plugin 14 | } 15 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/configuration/SearchConfigurationExtBuilderTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.configuration; 9 | 10 | import org.opensearch.core.common.bytes.BytesReference; 11 | import org.opensearch.common.io.stream.BytesStreamOutput; 12 | import org.opensearch.core.common.io.stream.StreamInput; 13 | import org.opensearch.core.common.io.stream.StreamOutput; 14 | import org.opensearch.common.settings.Settings; 15 | import org.opensearch.common.xcontent.XContentHelper; 16 | import org.opensearch.common.xcontent.XContentType; 17 | import org.opensearch.core.xcontent.XContentBuilder; 18 | import org.opensearch.core.xcontent.XContentParser; 19 | import org.opensearch.test.OpenSearchTestCase; 20 | 21 | import java.io.IOException; 22 | import java.util.Map; 23 | import java.util.Objects; 24 | 25 | public class SearchConfigurationExtBuilderTests extends OpenSearchTestCase { 26 | private static final String TRANSFORMER_NAME = "mock_transformer"; 27 | 28 | private static class MockResultTransformerConfiguration extends ResultTransformerConfiguration { 29 | private final String configuredValue; 30 | 31 | public MockResultTransformerConfiguration(String configuredValue) { 32 | this.configuredValue = configuredValue; 33 | } 34 | 35 | @Override 36 | public String getTransformerName() { 37 | return TRANSFORMER_NAME; 38 | } 39 | 40 | @Override 41 | public void writeTo(StreamOutput out) throws IOException { 42 | out.writeString(configuredValue); 43 | } 44 | 45 | @Override 46 | public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { 47 | builder.startObject(); 48 | builder.field("configuredValue", configuredValue); 49 | return builder.endObject(); 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (this == o) return true; 55 | if (o == null || getClass() != o.getClass()) return false; 56 | MockResultTransformerConfiguration that = (MockResultTransformerConfiguration) o; 57 | return configuredValue.equals(that.configuredValue); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | return Objects.hash(configuredValue); 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "MockResultTransformerConfiguration{" + 68 | "configuredValue='" + configuredValue + '\'' + 69 | '}'; 70 | } 71 | } 72 | 73 | private static ResultTransformerConfigurationFactory MOCK_RESULT_TRANSFORMER_CONFIGURATION_FACTORY = new ResultTransformerConfigurationFactory() { 74 | @Override 75 | public String getName() { 76 | return TRANSFORMER_NAME; 77 | } 78 | 79 | @Override 80 | public ResultTransformerConfiguration configure(Settings indexSettings) { 81 | return null; 82 | } 83 | 84 | @Override 85 | public ResultTransformerConfiguration configure(XContentParser parser) throws IOException { 86 | XContentParser.Token token = parser.nextToken(); 87 | assertSame(XContentParser.Token.FIELD_NAME, token); 88 | assertEquals("configuredValue", parser.currentName()); 89 | token = parser.nextToken(); 90 | assertSame(XContentParser.Token.VALUE_STRING, token); 91 | String configuredValue = parser.text(); 92 | return new MockResultTransformerConfiguration(configuredValue); 93 | } 94 | 95 | @Override 96 | public ResultTransformerConfiguration configure(StreamInput streamInput) throws IOException { 97 | return new MockResultTransformerConfiguration(streamInput.readString()); 98 | } 99 | }; 100 | public static final Map RESULT_TRANSFORMER_CONFIGURATION_FACTORY_MAP = Map.of(TRANSFORMER_NAME, MOCK_RESULT_TRANSFORMER_CONFIGURATION_FACTORY); 101 | 102 | 103 | public void testXContentRoundTrip() throws IOException { 104 | MockResultTransformerConfiguration configuration = new MockResultTransformerConfiguration(randomUnicodeOfLength(10)); 105 | SearchConfigurationExtBuilder searchConfigurationExtBuilder = new SearchConfigurationExtBuilder() 106 | .addResultTransformer(configuration); 107 | XContentType xContentType = randomFrom(XContentType.values()); 108 | BytesReference serialized = XContentHelper.toXContent(searchConfigurationExtBuilder, xContentType, true); 109 | 110 | XContentParser parser = createParser(xContentType.xContent(), serialized); 111 | 112 | SearchConfigurationExtBuilder deserialized = 113 | SearchConfigurationExtBuilder.parse(parser, RESULT_TRANSFORMER_CONFIGURATION_FACTORY_MAP); 114 | assertEquals(searchConfigurationExtBuilder, deserialized); 115 | } 116 | 117 | public void testStreamRoundTrip() throws IOException { 118 | MockResultTransformerConfiguration configuration = new MockResultTransformerConfiguration(randomUnicodeOfLength(10)); 119 | SearchConfigurationExtBuilder searchConfigurationExtBuilder = new SearchConfigurationExtBuilder() 120 | .addResultTransformer(configuration); 121 | BytesStreamOutput bytesStreamOutput = new BytesStreamOutput(); 122 | searchConfigurationExtBuilder.writeTo(bytesStreamOutput); 123 | 124 | SearchConfigurationExtBuilder deserialized = new SearchConfigurationExtBuilder(bytesStreamOutput.bytes().streamInput(), 125 | RESULT_TRANSFORMER_CONFIGURATION_FACTORY_MAP); 126 | assertEquals(searchConfigurationExtBuilder, deserialized); 127 | } 128 | } -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/client/KendraClientSettingsTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | * 8 | */ 9 | 10 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.client; 11 | 12 | import com.amazonaws.auth.AWSCredentials; 13 | import com.amazonaws.auth.AWSSessionCredentials; 14 | import org.opensearch.common.settings.MockSecureSettings; 15 | import org.opensearch.common.settings.SecureSettings; 16 | import org.opensearch.common.settings.Settings; 17 | import org.opensearch.common.settings.SettingsException; 18 | import org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration.KendraIntelligentRankerSettings; 19 | import org.opensearch.test.OpenSearchTestCase; 20 | 21 | import java.io.IOException; 22 | 23 | public class KendraClientSettingsTests extends OpenSearchTestCase { 24 | 25 | private static final String REGION = "us-west-2"; 26 | private static final String ENDPOINT = "http://localhost"; 27 | private static final String ACCESS_KEY = "my-access-key"; 28 | private static final String SECRET_KEY = "my-secret-key"; 29 | private static final String ASSUMED_ROLE = "assumed-role"; 30 | private static final String SESSION_TOKEN = "session-token"; 31 | 32 | 33 | private static KendraClientSettings buildClientSettings(boolean withAccessKey, boolean withSecretKey, 34 | boolean withSessionToken) throws IOException { 35 | try (MockSecureSettings secureSettings = new MockSecureSettings()) { 36 | if (withAccessKey) { 37 | secureSettings.setString(KendraIntelligentRankerSettings.ACCESS_KEY_SETTING.getKey(), ACCESS_KEY); 38 | } 39 | if (withSecretKey) { 40 | secureSettings.setString(KendraIntelligentRankerSettings.SECRET_KEY_SETTING.getKey(), SECRET_KEY); 41 | } 42 | if (withSessionToken) { 43 | secureSettings.setString(KendraIntelligentRankerSettings.SESSION_TOKEN_SETTING.getKey(), SESSION_TOKEN); 44 | } 45 | Settings settings = Settings.builder() 46 | .put(KendraIntelligentRankerSettings.SERVICE_REGION_SETTING.getKey(), REGION) 47 | .put(KendraIntelligentRankerSettings.SERVICE_ENDPOINT_SETTING.getKey(), ENDPOINT) 48 | .put(KendraIntelligentRankerSettings.ASSUME_ROLE_ARN_SETTING.getKey(), ASSUMED_ROLE) 49 | .setSecureSettings(secureSettings) 50 | .build(); 51 | 52 | return KendraClientSettings.getClientSettings(settings); 53 | } 54 | } 55 | 56 | public void testWithBasicCredentials() throws IOException { 57 | KendraClientSettings clientSettings = buildClientSettings(true, true, false); 58 | 59 | AWSCredentials credentials = clientSettings.getCredentials(); 60 | assertEquals(ACCESS_KEY, credentials.getAWSAccessKeyId()); 61 | assertEquals(SECRET_KEY, credentials.getAWSSecretKey()); 62 | assertFalse(credentials instanceof AWSSessionCredentials); 63 | assertEquals(REGION, clientSettings.getServiceRegion()); 64 | assertEquals(ENDPOINT, clientSettings.getServiceEndpoint()); 65 | assertEquals(ASSUMED_ROLE, clientSettings.getAssumeRoleArn()); 66 | } 67 | 68 | public void testWithSessionCredentials() throws IOException { 69 | KendraClientSettings clientSettings = buildClientSettings(true, true, true); 70 | 71 | AWSCredentials credentials = clientSettings.getCredentials(); 72 | assertEquals(ACCESS_KEY, credentials.getAWSAccessKeyId()); 73 | assertEquals(SECRET_KEY, credentials.getAWSSecretKey()); 74 | assertTrue(credentials instanceof AWSSessionCredentials); 75 | AWSSessionCredentials sessionCredentials = (AWSSessionCredentials) credentials; 76 | assertEquals(SESSION_TOKEN, sessionCredentials.getSessionToken()); 77 | assertEquals(REGION, clientSettings.getServiceRegion()); 78 | assertEquals(ENDPOINT, clientSettings.getServiceEndpoint()); 79 | assertEquals(ASSUMED_ROLE, clientSettings.getAssumeRoleArn()); 80 | } 81 | 82 | public void testWithoutCredentials() throws IOException { 83 | KendraClientSettings clientSettings = buildClientSettings(false, false, false); 84 | 85 | assertNull(clientSettings.getCredentials()); 86 | assertEquals(REGION, clientSettings.getServiceRegion()); 87 | assertEquals(ENDPOINT, clientSettings.getServiceEndpoint()); 88 | assertEquals(ASSUMED_ROLE, clientSettings.getAssumeRoleArn()); 89 | } 90 | 91 | public void testWithoutAccessKey() { 92 | expectThrows(SettingsException.class, () -> buildClientSettings(false, true, false)); 93 | expectThrows(SettingsException.class, () -> buildClientSettings(false, true, true)); 94 | } 95 | 96 | public void testWithoutSecretKey() { 97 | expectThrows(SettingsException.class, () -> buildClientSettings(true, false, false)); 98 | expectThrows(SettingsException.class, () -> buildClientSettings(true, false, true)); 99 | } 100 | 101 | public void testWithSessionTokenButNoCredentials() { 102 | expectThrows(SettingsException.class, () -> buildClientSettings(false, false, true)); 103 | } 104 | } -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/client/KendraHttpClientTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | * 8 | */ 9 | 10 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.client; 11 | 12 | import com.amazonaws.auth.BasicAWSCredentials; 13 | import com.amazonaws.http.IdleConnectionReaper; 14 | import org.opensearch.test.OpenSearchTestCase; 15 | 16 | import java.net.URI; 17 | 18 | public class KendraHttpClientTests extends OpenSearchTestCase { 19 | 20 | public void testCreateClient() throws Exception { 21 | 22 | BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials("accessKey", "secretKey"); 23 | KendraClientSettings settings = new KendraClientSettings(basicAWSCredentials, 24 | "http://localhost", 25 | "us-west-2", 26 | "12345678", 27 | "myAwesomeRole" 28 | ); 29 | 30 | try (KendraHttpClient client = new KendraHttpClient(settings)) { 31 | assertEquals(new URI("http://localhost/rescore-execution-plans/12345678/rescore"), client.buildRescoreURI()); 32 | } 33 | IdleConnectionReaper.shutdown(); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/client/KendraIntelligentClientTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.client; 9 | 10 | import org.mockito.Mockito; 11 | import org.opensearch.search.relevance.transformer.kendraintelligentranking.model.dto.RescoreRequest; 12 | import org.opensearch.search.relevance.transformer.kendraintelligentranking.model.dto.RescoreResult; 13 | import org.opensearch.test.OpenSearchTestCase; 14 | 15 | import java.util.function.Function; 16 | 17 | public class KendraIntelligentClientTests extends OpenSearchTestCase { 18 | protected static KendraHttpClient buildMockHttpClient(Function mockRescoreImpl) { 19 | KendraHttpClient kendraHttpClient = Mockito.mock(KendraHttpClient.class); 20 | Mockito.when(kendraHttpClient.isValid()).thenReturn(true); 21 | Mockito.doAnswer(invocation -> { 22 | RescoreRequest rescoreRequest = invocation.getArgument(0); 23 | return mockRescoreImpl.apply(rescoreRequest); 24 | }).when(kendraHttpClient).rescore(Mockito.any(RescoreRequest.class)); 25 | return kendraHttpClient; 26 | } 27 | 28 | protected static KendraHttpClient buildMockHttpClient() { 29 | return buildMockHttpClient(r -> new RescoreResult()); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/client/SimpleAwsErrorHandlerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | * 8 | */ 9 | 10 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.client; 11 | 12 | import com.amazonaws.AmazonServiceException; 13 | import com.amazonaws.DefaultRequest; 14 | import com.amazonaws.Request; 15 | import com.amazonaws.http.HttpResponse; 16 | import org.apache.http.HttpStatus; 17 | import org.opensearch.test.OpenSearchTestCase; 18 | 19 | import java.io.ByteArrayInputStream; 20 | import java.nio.charset.StandardCharsets; 21 | 22 | public class SimpleAwsErrorHandlerTests extends OpenSearchTestCase { 23 | 24 | private static final int STATUS_CODE = HttpStatus.SC_PAYMENT_REQUIRED; 25 | private static final String STATUS_TEXT = "Payment required"; 26 | private static final String SERVICE_NAME = "my-service"; 27 | private static final String ERROR_MESSAGE = "This is the error message"; 28 | 29 | public void testBehavior() throws Exception { 30 | SimpleAwsErrorHandler errorHandler = new SimpleAwsErrorHandler(); 31 | assertFalse(errorHandler.needsConnectionLeftOpen()); 32 | Request request = new DefaultRequest<>(SERVICE_NAME); 33 | HttpResponse httpResponse = new HttpResponse(request, null, null); 34 | httpResponse.setContent(new ByteArrayInputStream(ERROR_MESSAGE.getBytes(StandardCharsets.UTF_8))); 35 | httpResponse.setStatusCode(STATUS_CODE); 36 | httpResponse.setStatusText(STATUS_TEXT); 37 | 38 | AmazonServiceException ase = errorHandler.handle(httpResponse); 39 | 40 | assertEquals(ERROR_MESSAGE, ase.getErrorMessage()); 41 | assertEquals(STATUS_CODE, ase.getStatusCode()); 42 | assertEquals(STATUS_TEXT, ase.getErrorCode()); 43 | assertEquals(SERVICE_NAME, ase.getServiceName()); 44 | } 45 | } -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/configuration/KendraIntelligentRankingConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.configuration; 9 | 10 | import org.opensearch.core.common.bytes.BytesReference; 11 | import org.opensearch.common.io.stream.BytesStreamOutput; 12 | import org.opensearch.common.settings.Settings; 13 | import org.opensearch.common.xcontent.XContentHelper; 14 | import org.opensearch.common.xcontent.XContentType; 15 | import org.opensearch.core.xcontent.XContentParser; 16 | import org.opensearch.search.relevance.configuration.Constants; 17 | import org.opensearch.test.OpenSearchTestCase; 18 | 19 | import java.io.IOException; 20 | import java.util.List; 21 | 22 | public class KendraIntelligentRankingConfigurationTests extends OpenSearchTestCase { 23 | public void testParseWithNullParserAndContext() throws IOException { 24 | expectThrows(NullPointerException.class, () -> KendraIntelligentRankingConfiguration.parse(null)); 25 | } 26 | 27 | public void testSerializeToXContentRoundtrip() throws IOException { 28 | KendraIntelligentRankingConfiguration expected = getKendraIntelligentRankingConfiguration(); 29 | 30 | XContentType xContentType = randomFrom(XContentType.values()); 31 | BytesReference serialized = XContentHelper.toXContent(expected, xContentType, true); 32 | 33 | XContentParser parser = createParser(xContentType.xContent(), serialized); 34 | 35 | KendraIntelligentRankingConfiguration deserialized = 36 | KendraIntelligentRankingConfiguration.parse(parser); 37 | assertEquals(expected, deserialized); 38 | } 39 | 40 | public void testSerializeToStreamRoundtrip() throws IOException { 41 | KendraIntelligentRankingConfiguration expected = getKendraIntelligentRankingConfiguration(); 42 | BytesStreamOutput bytesStreamOutput = new BytesStreamOutput(); 43 | expected.writeTo(bytesStreamOutput); 44 | 45 | KendraIntelligentRankingConfiguration deserialized = 46 | new KendraIntelligentRankingConfiguration(bytesStreamOutput.bytes().streamInput()); 47 | assertEquals(expected, deserialized); 48 | } 49 | 50 | private static KendraIntelligentRankingConfiguration getKendraIntelligentRankingConfiguration() { 51 | int order = randomInt(10) + 1; 52 | int docLimit = randomInt( Integer.MAX_VALUE - 25) + 25; 53 | KendraIntelligentRankingConfiguration.KendraIntelligentRankingProperties properties = 54 | new KendraIntelligentRankingConfiguration.KendraIntelligentRankingProperties(List.of("body1"), 55 | List.of("title1"), docLimit); 56 | return new KendraIntelligentRankingConfiguration(order, properties); 57 | } 58 | 59 | public void testReadFromSettings() throws IOException { 60 | int order = randomInt(10); 61 | int docLimit = randomInt(100) + 25; 62 | String bodyField = "body1"; 63 | String titleField = "title1"; 64 | Settings settings = Settings.builder() 65 | .put(Constants.ORDER, order) 66 | .put("properties.doc_limit", docLimit) 67 | .put("properties.body_field", bodyField) 68 | .putList("properties.title_field", titleField) 69 | .build(); 70 | 71 | KendraIntelligentRankingConfiguration expected = new KendraIntelligentRankingConfiguration(order, 72 | new KendraIntelligentRankingConfiguration.KendraIntelligentRankingProperties(List.of(bodyField), 73 | List.of(titleField), docLimit)); 74 | 75 | KendraIntelligentRankingConfiguration actual = new KendraIntelligentRankingConfiguration(settings); 76 | 77 | assertEquals(expected, actual); 78 | } 79 | } -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/model/KendraIntelligentRankingExceptionTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | * 8 | */ 9 | 10 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.model; 11 | 12 | import org.opensearch.common.io.stream.BytesStreamOutput; 13 | import org.opensearch.test.OpenSearchTestCase; 14 | 15 | import java.io.IOException; 16 | 17 | public class KendraIntelligentRankingExceptionTests extends OpenSearchTestCase { 18 | 19 | public void testSerializationRoundtrip() throws IOException { 20 | KendraIntelligentRankingException expected = new KendraIntelligentRankingException("This is an error message"); 21 | BytesStreamOutput bytesStreamOutput = new BytesStreamOutput(); 22 | expected.writeTo(bytesStreamOutput); 23 | 24 | KendraIntelligentRankingException deserialized = 25 | new KendraIntelligentRankingException(bytesStreamOutput.bytes().streamInput()); 26 | assertEquals(expected.getMessage(), deserialized.getMessage()); 27 | } 28 | } -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/model/dto/DocumentTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | * 8 | */ 9 | 10 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.model.dto; 11 | 12 | 13 | import com.fasterxml.jackson.core.JsonProcessingException; 14 | import com.fasterxml.jackson.databind.ObjectMapper; 15 | import org.opensearch.test.OpenSearchTestCase; 16 | 17 | import java.util.List; 18 | 19 | public class DocumentTests extends OpenSearchTestCase { 20 | 21 | 22 | public static final String JSON_DOCUMENT = "{" + 23 | "\"Id\":\"docId\"," + 24 | "\"GroupId\":\"groupIp\"," + 25 | "\"TokenizedTitle\":[\"this\",\"is\",\"a\",\"title\"]," + 26 | "\"TokenizedBody\":[\"here\",\"lies\",\"the\",\"body\"]," + 27 | "\"OriginalScore\":2.71828" + 28 | "}"; 29 | public static final Document TEST_DOCUMENT = new Document("docId", 30 | "groupIp", 31 | List.of("this", "is", "a", "title"), 32 | List.of("here", "lies", "the", "body"), 33 | 2.71828f); 34 | 35 | public void testSerialization() throws JsonProcessingException { 36 | ObjectMapper objectMapper = new ObjectMapper(); 37 | String jsonForm = objectMapper.writeValueAsString(TEST_DOCUMENT); 38 | assertEquals(JSON_DOCUMENT, jsonForm); 39 | } 40 | 41 | public void testDeserialization() throws JsonProcessingException { 42 | ObjectMapper objectMapper = new ObjectMapper(); 43 | Document doc = objectMapper.readValue(JSON_DOCUMENT, Document.class); 44 | assertEquals(TEST_DOCUMENT, doc); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/preprocess/BM25ScorerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.preprocess; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | import org.junit.Assert; 14 | import org.opensearch.test.OpenSearchTestCase; 15 | 16 | public class BM25ScorerTests extends OpenSearchTestCase { 17 | 18 | public void testBM25Scorer() { 19 | TextTokenizer textTokenizer = new TextTokenizer(); 20 | List document1 = textTokenizer.tokenize( 21 | "OpenSearch is a distributed, community-driven, Apache 2.0-licensed, 100% open-source search and analytics suite used for a broad set of use cases like real-time application monitoring, log analytics, and website search. OpenSearch provides a highly scalable system for providing fast access and response to large volumes of data with an integrated visualization tool, OpenSearch Dashboards, that makes it easy for users to explore their data. OpenSearch is powered by the Apache Lucene search library, and it supports a number of search and analytics capabilities such as k-nearest neighbors (KNN) search, SQL, Anomaly Detection, Machine Learning Commons, Trace Analytics, full-text search, and more." 22 | ); 23 | List document2 = textTokenizer.tokenize( 24 | "OpenSearch enables you to easily ingest, secure, search, aggregate, view, and analyze data for a number of use cases such as log analytics, application search, enterprise search, and more. With OpenSearch, you benefit from having a 100% open source product you can use, modify, extend, monetize, and resell however you want. There are a growing number of OpenSearch Project partners that offer a variety of services such as professional support, enhanced features, and managed OpenSearch services. The OpenSearch Project continues to provide a secure, high-quality search and analytics suite with a rich roadmap of new and innovative functionality." 25 | ); 26 | List document3 = textTokenizer.tokenize( 27 | "The sky is blue" 28 | ); 29 | 30 | BM25Scorer bm25Scorer = new BM25Scorer(0.75, 1.6, Arrays.asList(document1, document2, document3)); 31 | 32 | List query1 = textTokenizer.tokenize("Apache Lucene search library"); 33 | double doc1Score1 = bm25Scorer.score(query1, document1); 34 | double doc2Score1 = bm25Scorer.score(query1, document2); 35 | double doc3Score1 = bm25Scorer.score(query1, document3); 36 | Assert.assertTrue(doc1Score1 > doc2Score1); 37 | Assert.assertTrue(doc2Score1 > doc3Score1); 38 | Assert.assertEquals(0, doc3Score1, 0); 39 | 40 | List query2 = textTokenizer.tokenize("sky color"); 41 | double doc1Score2 = bm25Scorer.score(query2, document1); 42 | double doc2Score2 = bm25Scorer.score(query2, document2); 43 | double doc3Score2 = bm25Scorer.score(query2, document3); 44 | Assert.assertTrue(doc3Score2 > doc1Score2); 45 | Assert.assertEquals(0, doc1Score2, 0); 46 | Assert.assertEquals(0, doc2Score2, 0); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/preprocess/SentenceSplitterTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.preprocess; 9 | 10 | import java.util.Arrays; 11 | import java.util.Collections; 12 | import java.util.List; 13 | import org.opensearch.test.OpenSearchTestCase; 14 | 15 | public class SentenceSplitterTests extends OpenSearchTestCase { 16 | 17 | private static final String TEXT_1 = "What is the capital of the United States?"; 18 | private static final String TEXT_2 = "OPENSEARCH IS OPEN SOURCE SEARCH AND ANALYTICS SUITE."; 19 | private static final String TEXT_3 = 20 | "You can install OpenSearch by following instructions at https://opensearch.org/docs/latest/opensearch/install/index/."; 21 | private static final String TEXT_4 = "Testing lots of spaces ! and a long word Pneumonoultramicroscopicsilicovolcanoconiosis"; 22 | 23 | private SentenceSplitter sentenceSplitter = new SentenceSplitter(); 24 | 25 | public void testSplit_BlankInput() { 26 | assertEquals(Collections.emptyList(), sentenceSplitter.split(null)); 27 | assertEquals(Collections.emptyList(), sentenceSplitter.split("")); 28 | assertEquals(Collections.emptyList(), sentenceSplitter.split(" ")); 29 | } 30 | 31 | public void testSplit() { 32 | final String text = String.join(" ", TEXT_1 + " ", TEXT_2, TEXT_3, TEXT_4); 33 | List splitSentences = sentenceSplitter.split(text); 34 | 35 | assertEquals(Arrays.asList(TEXT_1, TEXT_2, TEXT_3, 36 | "Testing lots of spaces !", 37 | "and a long word Pneumonoultramicroscopicsilicovolcanoconiosis"), splitSentences); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/test/java/org/opensearch/search/relevance/transformer/kendraintelligentranking/preprocess/TextTokenizerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.kendraintelligentranking.preprocess; 9 | 10 | import java.util.Arrays; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | import org.junit.Assert; 15 | import org.opensearch.test.OpenSearchTestCase; 16 | 17 | public class TextTokenizerTests extends OpenSearchTestCase { 18 | 19 | private static final String TEXT_1 = "What is the capital of the United States? "; 20 | private static final List EXPECTED_1 = Arrays.asList("capital", "united", "states"); 21 | private static final String TEXT_2 = "OPENSEARCH IS OPEN SOURCE SEARCH AND ANALYTICS SUITE NUMBER1."; 22 | private static final List EXPECTED_2 = Arrays.asList("opensearch", "open", "source", "search", "analytics", "suite", "number1"); 23 | private static final String TEXT_3 = 24 | "You can install OpenSearch by following instructions at https://opensearch.org/docs/latest/opensearch/install/index/"; 25 | private static final List EXPECTED_3 = Arrays.asList("install", "opensearch", "following", 26 | "instructions", "https", "opensearchorg", "docs", "latest", "opensearch", "install", "index"); 27 | private static final String TEXT_4 = "Testing lots of spaces and a long word Pneumonoultramicroscopicsilicovolcanoconiosis"; 28 | private static final List EXPECTED_4 = Arrays.asList("testing", "lots", "spaces", "long", "word", 29 | "pneumonoultramicroscopics", "ilicovolcanoconiosis"); 30 | 31 | private TextTokenizer textTokenizer = new TextTokenizer(); 32 | 33 | public void testTokenize() { 34 | List testCases = Arrays.asList(null, "", TEXT_1, TEXT_2, TEXT_3, TEXT_4); 35 | List> expectedResults = Arrays.asList(Collections.emptyList(), Collections.emptyList(), EXPECTED_1, EXPECTED_2, EXPECTED_3, EXPECTED_4); 36 | for (int i = 0; i < testCases.size(); ++i) { 37 | assertEquals("Test case " + testCases.get(i) + " failed", expectedResults.get(i), textTokenizer.tokenize(testCases.get(i))); 38 | } 39 | } 40 | 41 | public void testTokenizeMultiple() { 42 | List> actual = textTokenizer.tokenize(Arrays.asList(TEXT_1, TEXT_2, TEXT_3, TEXT_4)); 43 | assertEquals(Arrays.asList(EXPECTED_1, EXPECTED_2, EXPECTED_3, EXPECTED_4), actual); 44 | } 45 | 46 | public void testIsWordAllPunctuation() { 47 | List testCases = Arrays.asList(null, "", " ", "!@./?"); 48 | List expectedResults = Arrays.asList(false, false, false, true); 49 | for (int i = 0; i < testCases.size(); ++i) { 50 | assertEquals("Test case " + testCases.get(i) + " failed", expectedResults.get(i), textTokenizer.isWordAllPunctuation(testCases.get(i))); 51 | } 52 | } 53 | 54 | public void testIsNumeric() { 55 | List testCases = Arrays.asList(null, "", " ", "!@./?", "abc", " 22 ", "22", "5.028", "-20.0d"); 56 | List expectedResults = Arrays.asList(false, false, false, false, false, true, true, true, true); 57 | for (int i = 0; i < testCases.size(); ++i) { 58 | assertEquals("Test case " + testCases.get(i) + " failed", expectedResults.get(i), textTokenizer.isNumeric(testCases.get(i))); 59 | } 60 | } 61 | 62 | public void testRemoveInWordPunctuation() { 63 | List testCases = Arrays.asList(null, "", " ", "!@./?", "ab!!c", "a b!c,22"); 64 | List expectedResults = Arrays.asList(null, "", " ", "", "abc", "a bc22"); 65 | for (int i = 0; i < testCases.size(); ++i) { 66 | assertEquals("Test case " + testCases.get(i) + " failed", expectedResults.get(i), textTokenizer.removeInWordPunctuation(testCases.get(i))); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/yamlRestTest/java/org/opensearch/search/relevance/AmazonKendraIntelligentRankingClientYamlTestSuiteIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance; 9 | 10 | import com.carrotsearch.randomizedtesting.annotations.Name; 11 | import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; 12 | import org.opensearch.test.rest.yaml.ClientYamlTestCandidate; 13 | import org.opensearch.test.rest.yaml.OpenSearchClientYamlSuiteTestCase; 14 | 15 | 16 | public class AmazonKendraIntelligentRankingClientYamlTestSuiteIT extends OpenSearchClientYamlSuiteTestCase { 17 | 18 | public AmazonKendraIntelligentRankingClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { 19 | super(testCandidate); 20 | } 21 | 22 | @ParametersFactory 23 | public static Iterable parameters() throws Exception { 24 | return OpenSearchClientYamlSuiteTestCase.createParameters(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /amazon-kendra-intelligent-ranking/src/yamlRestTest/resources/rest-api-spec/test/10_basic.yml: -------------------------------------------------------------------------------- 1 | "Test that the plugin is loaded in OpenSearch": 2 | - do: 3 | cat.plugins: 4 | local: true 5 | h: component 6 | 7 | - match: 8 | $body: /^opensearch-amazon-kendra-intelligent-ranking-\d+.\d+.\d+.\d+\n$/ 9 | 10 | - do: 11 | indices.create: 12 | index: test 13 | 14 | - do: 15 | search: 16 | index: test 17 | body: { } 18 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/build.gradle: -------------------------------------------------------------------------------- 1 | import org.opensearch.gradle.test.RestIntegTestTask 2 | 3 | apply plugin: 'java' 4 | apply plugin: 'idea' 5 | apply plugin: 'opensearch.opensearchplugin' 6 | apply plugin: 'opensearch.yaml-rest-test' 7 | apply plugin: 'jacoco' 8 | 9 | group = 'org.opensearch' 10 | 11 | def pluginName = 'amazon-personalize-ranking' 12 | def pluginDescription = 'Rerank search results using Amazon Personalize' 13 | def projectPath = 'org.opensearch' 14 | def pathToPlugin = 'search.relevance' 15 | def pluginClassName = 'AmazonPersonalizeRankingPlugin' 16 | 17 | 18 | opensearchplugin { 19 | name "opensearch-${pluginName}-${plugin_version}.0" 20 | version "${plugin_version}" 21 | description pluginDescription 22 | classname "${projectPath}.${pathToPlugin}.${pluginClassName}" 23 | licenseFile rootProject.file('LICENSE') 24 | noticeFile rootProject.file('NOTICE') 25 | } 26 | 27 | java { 28 | targetCompatibility = JavaVersion.VERSION_21 29 | sourceCompatibility = JavaVersion.VERSION_21 30 | } 31 | 32 | // This requires an additional Jar not published as part of build-tools 33 | loggerUsageCheck.enabled = false 34 | 35 | // No need to validate pom, as we do not upload to maven/sonatype 36 | validateNebulaPom.enabled = false 37 | 38 | buildscript { 39 | repositories { 40 | mavenLocal() 41 | maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } 42 | mavenCentral() 43 | maven { url "https://plugins.gradle.org/m2/" } 44 | } 45 | 46 | dependencies { 47 | classpath "org.opensearch.gradle:build-tools:${opensearch_version}" 48 | } 49 | } 50 | 51 | repositories { 52 | mavenLocal() 53 | maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } 54 | mavenCentral() 55 | maven { url "https://plugins.gradle.org/m2/" } 56 | } 57 | 58 | dependencies { 59 | implementation 'org.apache.httpcomponents:httpclient:4.5.14' 60 | implementation 'org.apache.httpcomponents:httpcore:4.4.16' 61 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.2' 62 | implementation 'com.fasterxml.jackson.core:jackson-core:2.18.2' 63 | implementation 'com.fasterxml.jackson.core:jackson-annotations:2.18.2' 64 | implementation 'com.amazonaws:aws-java-sdk-sts:1.12.300' 65 | implementation 'com.amazonaws:aws-java-sdk-core:1.12.300' 66 | implementation 'com.amazonaws:aws-java-sdk-personalizeruntime:1.12.300' 67 | implementation 'commons-logging:commons-logging:1.2' 68 | } 69 | 70 | 71 | allprojects { 72 | plugins.withId('jacoco') { 73 | jacoco.toolVersion = '0.8.9' 74 | } 75 | } 76 | 77 | 78 | test { 79 | include '**/*Tests.class' 80 | finalizedBy jacocoTestReport 81 | } 82 | 83 | task integTest(type: RestIntegTestTask) { 84 | description = "Run tests against a cluster" 85 | testClassesDirs = sourceSets.test.output.classesDirs 86 | classpath = sourceSets.test.runtimeClasspath 87 | } 88 | tasks.named("check").configure { dependsOn(integTest) } 89 | 90 | integTest { 91 | // The --debug-jvm command-line option makes the cluster debuggable; this makes the tests debuggable 92 | if (System.getProperty("test.debug") != null) { 93 | jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005' 94 | } 95 | } 96 | 97 | testClusters.integTest { 98 | testDistribution = "ARCHIVE" 99 | 100 | // This installs our plugin into the testClusters 101 | plugin(project.tasks.bundlePlugin.archiveFile) 102 | } 103 | 104 | run { 105 | useCluster testClusters.integTest 106 | } 107 | 108 | sourceSets { 109 | main { 110 | resources { 111 | srcDirs = ["config"] 112 | includes = ["**/*.yml"] 113 | } 114 | } 115 | } 116 | 117 | 118 | jacocoTestReport { 119 | dependsOn test 120 | reports { 121 | xml.required = true 122 | html.required = true 123 | } 124 | } 125 | 126 | // TODO: Enable these checks 127 | dependencyLicenses.enabled = false 128 | thirdPartyAudit.enabled = false 129 | loggerUsageCheck.enabled = false 130 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/AmazonPersonalizeRankingPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance; 9 | 10 | import org.opensearch.client.Client; 11 | import org.opensearch.cluster.metadata.IndexNameExpressionResolver; 12 | import org.opensearch.cluster.service.ClusterService; 13 | import org.opensearch.common.settings.Setting; 14 | import org.opensearch.core.common.io.stream.NamedWriteableRegistry; 15 | import org.opensearch.core.xcontent.NamedXContentRegistry; 16 | import org.opensearch.env.Environment; 17 | import org.opensearch.env.NodeEnvironment; 18 | import org.opensearch.plugins.Plugin; 19 | import org.opensearch.plugins.SearchPipelinePlugin; 20 | import org.opensearch.plugins.SearchPlugin; 21 | import org.opensearch.repositories.RepositoriesService; 22 | import org.opensearch.script.ScriptService; 23 | import org.opensearch.search.pipeline.Processor; 24 | import org.opensearch.search.pipeline.SearchResponseProcessor; 25 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.PersonalizeRankingResponseProcessor; 26 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.client.PersonalizeClientSettings; 27 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.requestparameter.PersonalizeRequestParametersExtBuilder; 28 | import org.opensearch.threadpool.ThreadPool; 29 | import org.opensearch.watcher.ResourceWatcherService; 30 | 31 | import java.util.ArrayList; 32 | import java.util.Collection; 33 | import java.util.Collections; 34 | import java.util.List; 35 | import java.util.Map; 36 | import java.util.function.Supplier; 37 | import java.util.stream.Collectors; 38 | 39 | public class AmazonPersonalizeRankingPlugin extends Plugin implements SearchPlugin, SearchPipelinePlugin { 40 | 41 | private PersonalizeClientSettings personalizeClientSettings; 42 | 43 | @Override 44 | public List> getSettings() { 45 | // Add settings for other transformers here 46 | return new ArrayList<>(PersonalizeClientSettings.getAllSettings()); 47 | } 48 | 49 | @Override 50 | public Collection createComponents( 51 | Client client, 52 | ClusterService clusterService, 53 | ThreadPool threadPool, 54 | ResourceWatcherService resourceWatcherService, 55 | ScriptService scriptService, 56 | NamedXContentRegistry xContentRegistry, 57 | Environment environment, 58 | NodeEnvironment nodeEnvironment, 59 | NamedWriteableRegistry namedWriteableRegistry, 60 | IndexNameExpressionResolver indexNameExpressionResolver, 61 | Supplier repositoriesServiceSupplier 62 | ) { 63 | this.personalizeClientSettings = PersonalizeClientSettings.getClientSettings(environment.settings()); 64 | 65 | return Collections.emptyList(); 66 | } 67 | 68 | @Override 69 | public List> getSearchExts() { 70 | return List.of( 71 | new SearchPlugin.SearchExtSpec<>(PersonalizeRequestParametersExtBuilder.NAME, 72 | PersonalizeRequestParametersExtBuilder::new, 73 | PersonalizeRequestParametersExtBuilder::parse)); 74 | } 75 | 76 | @Override 77 | public Map> getResponseProcessors(Parameters parameters) { 78 | return Map.of(PersonalizeRankingResponseProcessor.TYPE, new PersonalizeRankingResponseProcessor.Factory(this.personalizeClientSettings)); 79 | } 80 | } -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/client/PersonalizeClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.client; 9 | 10 | import com.amazonaws.AmazonServiceException; 11 | import com.amazonaws.ClientConfiguration; 12 | import com.amazonaws.auth.AWSCredentialsProvider; 13 | import com.amazonaws.services.personalizeruntime.AmazonPersonalizeRuntime; 14 | import com.amazonaws.services.personalizeruntime.AmazonPersonalizeRuntimeClientBuilder; 15 | import com.amazonaws.services.personalizeruntime.model.GetPersonalizedRankingRequest; 16 | import com.amazonaws.services.personalizeruntime.model.GetPersonalizedRankingResult; 17 | 18 | import java.io.Closeable; 19 | import java.io.IOException; 20 | import java.security.AccessController; 21 | import java.security.PrivilegedAction; 22 | 23 | /** 24 | * Amazon Personalize client implementation for getting personalized ranking 25 | */ 26 | public class PersonalizeClient implements Closeable { 27 | private final AmazonPersonalizeRuntime personalizeRuntime; 28 | private static final String USER_AGENT_PREFIX = "PersonalizeOpenSearchPlugin"; 29 | 30 | /** 31 | * Constructor for Amazon Personalize client 32 | * @param credentialsProvider Credentials to be used for accessing Amazon Personalize 33 | * @param awsRegion AWS region where Amazon Personalize campaign is hosted 34 | */ 35 | public PersonalizeClient(AWSCredentialsProvider credentialsProvider, String awsRegion) { 36 | ClientConfiguration clientConfiguration = AccessController.doPrivileged( 37 | (PrivilegedAction) () -> new ClientConfiguration() 38 | .withUserAgentPrefix(USER_AGENT_PREFIX)); 39 | personalizeRuntime = AccessController.doPrivileged( 40 | (PrivilegedAction) () -> AmazonPersonalizeRuntimeClientBuilder.standard() 41 | .withCredentials(credentialsProvider) 42 | .withRegion(awsRegion) 43 | .withClientConfiguration(clientConfiguration) 44 | .build()); 45 | } 46 | 47 | /** 48 | * Get Personalize runtime client 49 | * @return Personalize runtime client 50 | */ 51 | public AmazonPersonalizeRuntime getPersonalizeRuntime() { 52 | return personalizeRuntime; 53 | } 54 | 55 | /** 56 | * Get Personalized ranking using Personalized runtime client 57 | * @param request Get personalized ranking request 58 | * @return Personalized ranking results 59 | */ 60 | public GetPersonalizedRankingResult getPersonalizedRanking(GetPersonalizedRankingRequest request) { 61 | GetPersonalizedRankingResult result; 62 | try { 63 | result = AccessController.doPrivileged( 64 | (PrivilegedAction) () -> personalizeRuntime.getPersonalizedRanking(request)); 65 | } catch (AmazonServiceException ex) { 66 | throw ex; 67 | } 68 | return result; 69 | } 70 | 71 | @Override 72 | public void close() throws IOException { 73 | if (personalizeRuntime != null) { 74 | personalizeRuntime.shutdown(); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/client/PersonalizeClientSettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.client; 9 | 10 | import com.amazonaws.auth.AWSCredentials; 11 | import com.amazonaws.auth.BasicAWSCredentials; 12 | import com.amazonaws.auth.BasicSessionCredentials; 13 | import org.apache.logging.log4j.LogManager; 14 | import org.apache.logging.log4j.Logger; 15 | import org.opensearch.core.common.settings.SecureString; 16 | import org.opensearch.common.settings.SecureSetting; 17 | import org.opensearch.common.settings.Setting; 18 | import org.opensearch.common.settings.Settings; 19 | import org.opensearch.common.settings.SettingsException; 20 | 21 | import java.util.Arrays; 22 | import java.util.Collection; 23 | 24 | /** 25 | * Container for personalize client settings such as AWS credentials 26 | */ 27 | public final class PersonalizeClientSettings { 28 | 29 | private static final Logger logger = LogManager.getLogger(PersonalizeClientSettings.class); 30 | 31 | /** 32 | * The access key (ie login id) for connecting to Personalize. 33 | */ 34 | public static final Setting ACCESS_KEY_SETTING = SecureSetting.secureString("personalized_search_ranking.aws.access_key", null); 35 | 36 | /** 37 | * The secret key (ie password) for connecting to Personalize. 38 | */ 39 | public static final Setting SECRET_KEY_SETTING = SecureSetting.secureString("personalized_search_ranking.aws.secret_key", null); 40 | 41 | /** 42 | * The session token for connecting to Personalize. 43 | */ 44 | public static final Setting SESSION_TOKEN_SETTING = SecureSetting.secureString("personalized_search_ranking.aws.session_token", null); 45 | 46 | private final AWSCredentials credentials; 47 | 48 | protected PersonalizeClientSettings(AWSCredentials credentials) { 49 | this.credentials = credentials; 50 | } 51 | 52 | public static Collection> getAllSettings() { 53 | return Arrays.asList( 54 | ACCESS_KEY_SETTING, 55 | SECRET_KEY_SETTING, 56 | SESSION_TOKEN_SETTING 57 | ); 58 | } 59 | 60 | public AWSCredentials getCredentials() { 61 | return credentials; 62 | } 63 | 64 | /** 65 | * Load AWS credentials from open search keystore if available 66 | * @param settings Open search settings 67 | * @return AWS credentials 68 | */ 69 | static AWSCredentials loadCredentials(Settings settings) { 70 | try (SecureString key = ACCESS_KEY_SETTING.get(settings); 71 | SecureString secret = SECRET_KEY_SETTING.get(settings); 72 | SecureString sessionToken = SESSION_TOKEN_SETTING.get(settings)) { 73 | if (key.length() == 0 && secret.length() == 0) { 74 | if (sessionToken.length() > 0) { 75 | throw new SettingsException("Setting [{}] is set but [{}] and [{}] are not", 76 | SESSION_TOKEN_SETTING.getKey(), ACCESS_KEY_SETTING.getKey(), SECRET_KEY_SETTING.getKey()); 77 | } 78 | logger.info("Using either environment variables, system properties or instance profile credentials"); 79 | return null; 80 | } else if (key.length() == 0 || secret.length() == 0) { 81 | throw new SettingsException("One of settings [{}] and [{}] is not set.", 82 | ACCESS_KEY_SETTING.getKey(), SECRET_KEY_SETTING.getKey()); 83 | } else { 84 | final AWSCredentials credentials; 85 | if (sessionToken.length() == 0) { 86 | logger.info("Using basic key/secret credentials"); 87 | credentials = new BasicAWSCredentials(key.toString(), secret.toString()); 88 | } else { 89 | logger.info("Using basic session credentials"); 90 | credentials = new BasicSessionCredentials(key.toString(), secret.toString(), sessionToken.toString()); 91 | } 92 | return credentials; 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * Get Personalize client settings 99 | * @param settings Open search settings 100 | * @return Personalize client settings instance with AWS credentials 101 | */ 102 | public static PersonalizeClientSettings getClientSettings(Settings settings) { 103 | final AWSCredentials credentials = loadCredentials(settings); 104 | return new PersonalizeClientSettings(credentials); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/client/PersonalizeCredentialsProviderFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.client; 9 | 10 | import com.amazonaws.auth.AWSCredentials; 11 | import com.amazonaws.auth.AWSCredentialsProvider; 12 | import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; 13 | import com.amazonaws.auth.AWSStaticCredentialsProvider; 14 | import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; 15 | import com.amazonaws.services.securitytoken.AWSSecurityTokenService; 16 | import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; 17 | import org.apache.logging.log4j.LogManager; 18 | import org.apache.logging.log4j.Logger; 19 | 20 | import java.security.AccessController; 21 | import java.security.PrivilegedAction; 22 | 23 | /** 24 | * Factory implementation for getting Personalize credentials 25 | */ 26 | public final class PersonalizeCredentialsProviderFactory { 27 | private static final Logger logger = LogManager.getLogger(PersonalizeCredentialsProviderFactory.class); 28 | private static final String ASSUME_ROLE_SESSION_NAME = "OpenSearchPersonalizeIntelligentRankingPluginSession"; 29 | 30 | private PersonalizeCredentialsProviderFactory() { 31 | } 32 | 33 | /** 34 | * Get AWS credentials provider either from static credentials from open search keystore or 35 | * using DefaultAWSCredentialsProviderChain. 36 | * @param clientSettings Personalize client settings 37 | * @return AWS credentials provider for accessing Personalize 38 | */ 39 | static AWSCredentialsProvider getCredentialsProvider(PersonalizeClientSettings clientSettings) { 40 | final AWSCredentialsProvider credentialsProvider; 41 | final AWSCredentials credentials = clientSettings.getCredentials(); 42 | if (credentials == null) { 43 | logger.info("Credentials not present in open search keystore. Using DefaultAWSCredentialsProviderChain for credentials."); 44 | credentialsProvider = AccessController.doPrivileged( 45 | (PrivilegedAction) () -> DefaultAWSCredentialsProviderChain.getInstance()); 46 | } else { 47 | logger.info("Using credentials provided in open search keystore"); 48 | credentialsProvider = AccessController.doPrivileged( 49 | (PrivilegedAction) () -> new AWSStaticCredentialsProvider(credentials)); 50 | } 51 | return credentialsProvider; 52 | } 53 | 54 | /** 55 | * Get AWS credentials provider by assuming IAM role if provided or else 56 | * use static credentials or DefaultAWSCredentialsProviderChain. 57 | * @param clientSettings Personalize client settings 58 | * @param personalizeIAMRole IAM role configuration for accessing Personalize 59 | * @param awsRegion AWS region 60 | * @return AWS credentials provider for accessing Amazon Personalize 61 | */ 62 | public static AWSCredentialsProvider getCredentialsProvider(PersonalizeClientSettings clientSettings, 63 | String personalizeIAMRole, 64 | String awsRegion) { 65 | 66 | final AWSCredentialsProvider credentialsProvider; 67 | AWSCredentialsProvider baseCredentialsProvider = getCredentialsProvider(clientSettings); 68 | 69 | if (personalizeIAMRole != null && !personalizeIAMRole.isBlank()) { 70 | logger.info("Using IAM Role provided to access Personalize."); 71 | // If IAM role ARN was provided in config, then use auto-refreshed role credentials. 72 | credentialsProvider = AccessController.doPrivileged( 73 | (PrivilegedAction) () -> { 74 | AWSSecurityTokenService awsSecurityTokenService = AWSSecurityTokenServiceClientBuilder.standard() 75 | .withCredentials(baseCredentialsProvider) 76 | .withRegion(awsRegion) 77 | .build(); 78 | 79 | return new STSAssumeRoleSessionCredentialsProvider.Builder(personalizeIAMRole, ASSUME_ROLE_SESSION_NAME) 80 | .withStsClient(awsSecurityTokenService) 81 | .build(); 82 | }); 83 | } else { 84 | logger.info("IAM Role for accessing Personalize is not provided."); 85 | credentialsProvider = baseCredentialsProvider; 86 | } 87 | return credentialsProvider; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/configuration/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | 9 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.configuration; 10 | 11 | /** 12 | * Constants for Amazon Perosnalize response processor 13 | */ 14 | public class Constants { 15 | public static final String AMAZON_PERSONALIZED_RANKING_RECIPE_NAME = "aws-personalized-ranking"; 16 | public static final String AMAZON_PERSONALIZED_RANKING_V2_RECIPE_NAME = "aws-personalized-ranking-v2"; 17 | } 18 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/configuration/PersonalizeIntelligentRankerConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.configuration; 9 | 10 | /** 11 | * A container for holding Personalize ranker configuration 12 | */ 13 | public class PersonalizeIntelligentRankerConfiguration { 14 | private final String personalizeCampaign; 15 | private final String iamRoleArn; 16 | private final String recipe; 17 | private final String itemIdField; 18 | private final String region; 19 | private final double weight; 20 | 21 | /** 22 | * 23 | * @param personalizeCampaign Personalize campaign 24 | * @param iamRoleArn IAM Role ARN for accessing Personalize campaign 25 | * @param recipe Personalize recipe associated with campaign 26 | * @param itemIdField Item ID field to pick up item id for Personalize input 27 | * @param region AWS region 28 | * @param weight Configurable coefficient to control Personalization of search results 29 | */ 30 | public PersonalizeIntelligentRankerConfiguration(String personalizeCampaign, 31 | String iamRoleArn, 32 | String recipe, 33 | String itemIdField, 34 | String region, 35 | double weight) { 36 | this.personalizeCampaign = personalizeCampaign; 37 | this.iamRoleArn = iamRoleArn; 38 | this.recipe = recipe; 39 | this.itemIdField = itemIdField; 40 | this.region = region; 41 | this.weight = weight; 42 | } 43 | 44 | /** 45 | * Get PErsonalize campaign 46 | * @return Personalize campaign 47 | */ 48 | public String getPersonalizeCampaign() { 49 | return personalizeCampaign; 50 | } 51 | 52 | /** 53 | * Get recipe 54 | * @return Recipe associated with Personalize campaign 55 | */ 56 | public String getRecipe() { 57 | return recipe; 58 | } 59 | 60 | /** 61 | * Get Item ID field 62 | * @return Item ID field 63 | */ 64 | public String getItemIdField() { 65 | return itemIdField; 66 | } 67 | 68 | /** 69 | * Get AWS region 70 | * @return AWS region 71 | */ 72 | public String getRegion() { 73 | return region; 74 | } 75 | 76 | /** 77 | * 78 | * @return weight value 79 | */ 80 | public double getWeight() { 81 | return weight; 82 | } 83 | 84 | /** 85 | * Get IAM role ARN for Personalize campaign 86 | * @return IAM role for accessing Personalize campaign 87 | */ 88 | public String getIamRoleArn() { 89 | return iamRoleArn; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/requestparameter/PersonalizeRequestParameterUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.requestparameter; 9 | 10 | import org.opensearch.action.search.SearchRequest; 11 | import org.opensearch.search.SearchExtBuilder; 12 | 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | public class PersonalizeRequestParameterUtil { 17 | 18 | public static PersonalizeRequestParameters getPersonalizeRequestParameters(SearchRequest searchRequest) { 19 | PersonalizeRequestParametersExtBuilder personalizeRequestParameterExtBuilder = null; 20 | if (searchRequest.source() != null && searchRequest.source().ext() != null && !searchRequest.source().ext().isEmpty()) { 21 | List extBuilders = searchRequest.source().ext().stream() 22 | .filter(extBuilder -> PersonalizeRequestParametersExtBuilder.NAME.equals(extBuilder.getWriteableName())) 23 | .collect(Collectors.toList()); 24 | 25 | if (!extBuilders.isEmpty()) { 26 | personalizeRequestParameterExtBuilder = (PersonalizeRequestParametersExtBuilder) extBuilders.get(0); 27 | } 28 | } 29 | PersonalizeRequestParameters personalizeRequestParameters = null; 30 | if (personalizeRequestParameterExtBuilder != null) { 31 | personalizeRequestParameters = personalizeRequestParameterExtBuilder.getRequestParameters(); 32 | } 33 | return personalizeRequestParameters; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/requestparameter/PersonalizeRequestParameters.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.requestparameter; 9 | 10 | import org.opensearch.core.common.io.stream.StreamInput; 11 | import org.opensearch.core.common.io.stream.StreamOutput; 12 | import org.opensearch.core.common.io.stream.Writeable; 13 | import org.opensearch.core.ParseField; 14 | import org.opensearch.core.xcontent.ObjectParser; 15 | import org.opensearch.core.xcontent.ToXContentObject; 16 | import org.opensearch.core.xcontent.XContentBuilder; 17 | import org.opensearch.core.xcontent.XContentParser; 18 | 19 | import java.io.IOException; 20 | import java.util.Map; 21 | import java.util.Objects; 22 | 23 | public class PersonalizeRequestParameters implements Writeable, ToXContentObject { 24 | 25 | static final String PERSONALIZE_REQUEST_PARAMETERS = "personalize_request_parameters"; 26 | private static final String USER_ID_PARAMETER = "user_id"; 27 | private static final String CONTEXT_PARAMETER = "context"; 28 | 29 | private static final ObjectParser PARSER; 30 | private static final ParseField USER_ID = new ParseField(USER_ID_PARAMETER); 31 | private static final ParseField CONTEXT = new ParseField(CONTEXT_PARAMETER); 32 | 33 | static { 34 | PARSER = new ObjectParser<>(PERSONALIZE_REQUEST_PARAMETERS, PersonalizeRequestParameters::new); 35 | PARSER.declareString(PersonalizeRequestParameters::setUserId, USER_ID); 36 | PARSER.declareObject(PersonalizeRequestParameters::setContext,(XContentParser p, Void c) -> { 37 | try { 38 | return p.map(); 39 | } catch (IOException e) { 40 | throw new IllegalArgumentException("Error parsing Personalize context from request parameters", e); 41 | } 42 | }, CONTEXT); 43 | } 44 | 45 | private String userId; 46 | 47 | private Map context; 48 | 49 | public PersonalizeRequestParameters() {} 50 | 51 | public PersonalizeRequestParameters(String userId, Map context) { 52 | this.userId = userId; 53 | this.context = context; 54 | } 55 | 56 | public PersonalizeRequestParameters(StreamInput input) throws IOException { 57 | this.userId = input.readString(); 58 | this.context = input.readMap(); 59 | } 60 | 61 | public String getUserId() { 62 | return userId; 63 | } 64 | 65 | public void setUserId(String userId) { 66 | this.userId = userId; 67 | } 68 | 69 | public Map getContext() { 70 | return context; 71 | } 72 | 73 | public void setContext(Map context) { 74 | this.context = context; 75 | } 76 | 77 | @Override 78 | public void writeTo(StreamOutput out) throws IOException { 79 | out.writeString(this.userId); 80 | out.writeMap(this.context); 81 | } 82 | 83 | @Override 84 | public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { 85 | builder.field(USER_ID.getPreferredName(), this.userId); 86 | return builder.field(CONTEXT.getPreferredName(), this.context); 87 | } 88 | 89 | public static PersonalizeRequestParameters parse(XContentParser parser) throws IOException { 90 | PersonalizeRequestParameters requestParameters = PARSER.parse(parser, null); 91 | return requestParameters; 92 | } 93 | 94 | @Override 95 | public boolean equals(Object o) { 96 | if (this == o) return true; 97 | if (o == null || getClass() != o.getClass()) return false; 98 | 99 | PersonalizeRequestParameters config = (PersonalizeRequestParameters) o; 100 | 101 | if (!userId.equals(config.userId)) return false; 102 | if (context.size() != config.getContext().size()) return false; 103 | return userId.equals(config.userId) && context.equals(config.getContext()); 104 | } 105 | 106 | @Override 107 | public int hashCode() { 108 | return Objects.hash(userId, context); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/requestparameter/PersonalizeRequestParametersExtBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.requestparameter; 9 | 10 | import org.apache.logging.log4j.LogManager; 11 | import org.apache.logging.log4j.Logger; 12 | import org.opensearch.core.common.io.stream.StreamInput; 13 | import org.opensearch.core.common.io.stream.StreamOutput; 14 | import org.opensearch.core.xcontent.XContentBuilder; 15 | import org.opensearch.core.xcontent.XContentParser; 16 | import org.opensearch.search.SearchExtBuilder; 17 | 18 | import java.io.IOException; 19 | import java.util.Objects; 20 | 21 | import static org.opensearch.search.relevance.transformer.personalizeintelligentranking.requestparameter.PersonalizeRequestParameters.PERSONALIZE_REQUEST_PARAMETERS; 22 | 23 | public class PersonalizeRequestParametersExtBuilder extends SearchExtBuilder { 24 | private static final Logger logger = LogManager.getLogger(PersonalizeRequestParametersExtBuilder.class); 25 | public static final String NAME = PERSONALIZE_REQUEST_PARAMETERS; 26 | private PersonalizeRequestParameters requestParameters; 27 | 28 | public PersonalizeRequestParametersExtBuilder() {} 29 | 30 | public PersonalizeRequestParametersExtBuilder(StreamInput input) throws IOException { 31 | requestParameters = new PersonalizeRequestParameters(input); 32 | } 33 | 34 | public PersonalizeRequestParameters getRequestParameters() { 35 | return requestParameters; 36 | } 37 | 38 | public void setRequestParameters(PersonalizeRequestParameters requestParameters) { 39 | this.requestParameters = requestParameters; 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return Objects.hash(this.getClass(), this.requestParameters); 45 | } 46 | 47 | @Override 48 | public boolean equals(Object obj) { 49 | if (obj == null) { 50 | return false; 51 | } 52 | if (!(obj instanceof PersonalizeRequestParametersExtBuilder)) { 53 | return false; 54 | } 55 | PersonalizeRequestParametersExtBuilder o = (PersonalizeRequestParametersExtBuilder) obj; 56 | return this.requestParameters.equals(o.requestParameters); 57 | } 58 | 59 | @Override 60 | public String getWriteableName() { 61 | return NAME; 62 | } 63 | 64 | @Override 65 | public void writeTo(StreamOutput out) throws IOException { 66 | requestParameters.writeTo(out); 67 | } 68 | 69 | @Override 70 | public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { 71 | return builder.value(requestParameters); 72 | } 73 | 74 | public static PersonalizeRequestParametersExtBuilder parse(XContentParser parser) throws IOException{ 75 | PersonalizeRequestParametersExtBuilder extBuilder = new PersonalizeRequestParametersExtBuilder(); 76 | PersonalizeRequestParameters requestParameters = PersonalizeRequestParameters.parse(parser); 77 | extBuilder.setRequestParameters(requestParameters); 78 | return extBuilder; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/reranker/PersonalizedRanker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.reranker; 9 | 10 | import org.opensearch.search.SearchHits; 11 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.requestparameter.PersonalizeRequestParameters; 12 | 13 | public interface PersonalizedRanker { 14 | 15 | /** 16 | * Re rank search hits 17 | * @param hits Search hits to re rank 18 | * @param requestParameters Request parameters for Personalize present in search request 19 | * @return Re ranked search hits 20 | */ 21 | SearchHits rerank(SearchHits hits, PersonalizeRequestParameters requestParameters); 22 | } 23 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/reranker/PersonalizedRankerFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.reranker; 9 | 10 | import org.apache.logging.log4j.LogManager; 11 | import org.apache.logging.log4j.Logger; 12 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.client.PersonalizeClient; 13 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.configuration.PersonalizeIntelligentRankerConfiguration; 14 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.reranker.impl.AmazonPersonalizedRankerImpl; 15 | 16 | import static org.opensearch.search.relevance.transformer.personalizeintelligentranking.configuration.Constants.AMAZON_PERSONALIZED_RANKING_RECIPE_NAME; 17 | import static org.opensearch.search.relevance.transformer.personalizeintelligentranking.configuration.Constants.AMAZON_PERSONALIZED_RANKING_V2_RECIPE_NAME; 18 | 19 | /** 20 | * Factory for creating Personalize ranker instance based on Personalize ranker configuration 21 | */ 22 | public class PersonalizedRankerFactory { 23 | private static final Logger logger = LogManager.getLogger(PersonalizedRankerFactory.class); 24 | 25 | /** 26 | * Create an instance of Personalize ranker based on ranker configuration 27 | * @param config Personalize ranker configuration 28 | * @param client Personalize client 29 | * @return Personalize ranker instance 30 | */ 31 | public PersonalizedRanker getPersonalizedRanker(PersonalizeIntelligentRankerConfiguration config, PersonalizeClient client){ 32 | PersonalizedRanker ranker = null; 33 | String recipeInConfig = config.getRecipe(); 34 | if (recipeInConfig.equals(AMAZON_PERSONALIZED_RANKING_RECIPE_NAME) 35 | || recipeInConfig.equals(AMAZON_PERSONALIZED_RANKING_V2_RECIPE_NAME)) { 36 | ranker = new AmazonPersonalizedRankerImpl(config, client); 37 | } else { 38 | logger.error("Personalize recipe provided in configuration is not supported for re ranking search results"); 39 | //TODO : throw user error exception 40 | } 41 | return ranker; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/utils/ValidationUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.utils; 9 | import com.amazonaws.arn.Arn; 10 | import org.opensearch.ingest.ConfigurationUtils; 11 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.configuration.PersonalizeIntelligentRankerConfiguration; 12 | 13 | import java.util.Arrays; 14 | import java.util.Set; 15 | import java.util.HashSet; 16 | 17 | import static org.opensearch.search.relevance.transformer.personalizeintelligentranking.configuration.Constants.AMAZON_PERSONALIZED_RANKING_RECIPE_NAME; 18 | import static org.opensearch.search.relevance.transformer.personalizeintelligentranking.configuration.Constants.AMAZON_PERSONALIZED_RANKING_V2_RECIPE_NAME; 19 | 20 | public class ValidationUtil { 21 | private static Set SUPPORTED_PERSONALIZE_RECIPES = new HashSet<>(Arrays.asList( 22 | AMAZON_PERSONALIZED_RANKING_RECIPE_NAME, 23 | AMAZON_PERSONALIZED_RANKING_V2_RECIPE_NAME 24 | )); 25 | 26 | /** 27 | * Validate Personalize configuration for calling Personalize service. 28 | * Throws OpenSearchParseException type exception if validation fails. 29 | * @param config Personalize intelligent ranker configuration 30 | * @param processorType Name of search pipeline processor 31 | * @param processorTag Name of processor tag 32 | */ 33 | public static void validatePersonalizeIntelligentRankerConfiguration (PersonalizeIntelligentRankerConfiguration config, 34 | String processorType, 35 | String processorTag 36 | ) { 37 | // Validate weight value 38 | if (config.getWeight() < 0.0 || config.getWeight() > 1.0) { 39 | throw ConfigurationUtils.newConfigurationException(processorType, processorTag, "weight", "invalid value for weight"); 40 | } 41 | // Validate Personalize campaign ARN 42 | if(!isValidCampaignOrRoleArn(config.getPersonalizeCampaign(), "personalize")) { 43 | throw ConfigurationUtils.newConfigurationException(processorType, processorTag, "campaign_arn", "invalid format for Personalize campaign arn"); 44 | } 45 | // Validate IAM Role Arn for Personalize access 46 | String iamRoleArn = config.getIamRoleArn(); 47 | if(!(iamRoleArn == null || iamRoleArn.isBlank()) && !isValidCampaignOrRoleArn(iamRoleArn, "iam")) { 48 | throw ConfigurationUtils.newConfigurationException(processorType, processorTag, "iam_role_arn", "invalid format for Personalize iam role arn"); 49 | } 50 | // Validate Personalize recipe 51 | if(!SUPPORTED_PERSONALIZE_RECIPES.contains(config.getRecipe())) { 52 | throw ConfigurationUtils.newConfigurationException(processorType, processorTag, "recipe", "not supported recipe provided"); 53 | } 54 | } 55 | 56 | private static boolean isValidCampaignOrRoleArn(String arn, String expectedService) { 57 | try { 58 | Arn arnObj = Arn.fromString(arn); 59 | return arnObj.getService().equals(expectedService); 60 | } catch (IllegalArgumentException iae) { 61 | return false; 62 | } 63 | } 64 | 65 | public static boolean is4xxError(int statusCode){ 66 | if (statusCode >= 400 && statusCode < 500) { 67 | return true; 68 | } 69 | return false; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/main/plugin-metadata/plugin-security.policy: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | 9 | grant { 10 | permission java.lang.RuntimePermission "accessDeclaredMembers"; 11 | permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; 12 | 13 | permission java.net.SocketPermission "*", "connect,resolve"; 14 | permission java.lang.RuntimePermission "getClassLoader"; 15 | }; 16 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/test/java/org/opensearch/search/relevance/AmazonPersonalizeRankingPluginIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance; 9 | 10 | import org.apache.hc.core5.http.ParseException; 11 | import org.apache.hc.core5.http.io.entity.EntityUtils; 12 | import org.opensearch.client.Request; 13 | import org.opensearch.client.Response; 14 | import org.opensearch.test.rest.OpenSearchRestTestCase; 15 | 16 | import java.io.IOException; 17 | 18 | public class AmazonPersonalizeRankingPluginIT extends OpenSearchRestTestCase { 19 | 20 | public void testPluginInstalled() throws IOException, ParseException { 21 | Response response = client().performRequest(new Request("GET", "/_cat/plugins")); 22 | String body = EntityUtils.toString(response.getEntity()); 23 | 24 | logger.info("response body: {}", body); 25 | assertNotNull(body); 26 | assertTrue(body.contains("amazon-personalize-ranking")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/test/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/client/PersonalizeClientSettingsTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.client; 9 | 10 | import com.amazonaws.auth.AWSCredentials; 11 | import com.amazonaws.auth.AWSSessionCredentials; 12 | import org.opensearch.common.settings.SecureSetting; 13 | import org.opensearch.common.settings.Setting; 14 | import org.opensearch.common.settings.SettingsException; 15 | import org.opensearch.core.common.settings.SecureString; 16 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.utils.PersonalizeClientSettingsTestUtil; 17 | import org.opensearch.test.OpenSearchTestCase; 18 | 19 | import java.io.IOException; 20 | import java.util.Arrays; 21 | import java.util.Collection; 22 | import java.util.List; 23 | 24 | import static org.opensearch.search.relevance.transformer.personalizeintelligentranking.utils.PersonalizeClientSettingsTestUtil.ACCESS_KEY; 25 | import static org.opensearch.search.relevance.transformer.personalizeintelligentranking.utils.PersonalizeClientSettingsTestUtil.SECRET_KEY; 26 | import static org.opensearch.search.relevance.transformer.personalizeintelligentranking.utils.PersonalizeClientSettingsTestUtil.SESSION_TOKEN; 27 | 28 | public class PersonalizeClientSettingsTests extends OpenSearchTestCase { 29 | 30 | public void testWithBasicCredentials() throws IOException { 31 | PersonalizeClientSettings clientSettings = PersonalizeClientSettingsTestUtil.buildClientSettings(true, true, false); 32 | AWSCredentials credentials = clientSettings.getCredentials(); 33 | assertEquals(ACCESS_KEY, credentials.getAWSAccessKeyId()); 34 | assertEquals(SECRET_KEY, credentials.getAWSSecretKey()); 35 | assertFalse(credentials instanceof AWSSessionCredentials); 36 | } 37 | 38 | public void testWithGetAllSetting() throws IOException { 39 | PersonalizeClientSettings clientSettings = PersonalizeClientSettingsTestUtil.buildClientSettings(true, true, true); 40 | assertEquals(clientSettings.getAllSettings().size(), 3); 41 | Setting ACCESS_KEY_SETTING = SecureSetting.secureString("personalized_search_ranking.aws.access_key", null); 42 | Setting SECRET_KEY_SETTING = SecureSetting.secureString("personalized_search_ranking.aws.secret_key", null); 43 | Setting SESSION_TOKEN_SETTING = SecureSetting.secureString("personalized_search_ranking.aws.session_token", null); 44 | assertEquals(ACCESS_KEY_SETTING, clientSettings.getAllSettings().toArray()[0]); 45 | assertEquals(SECRET_KEY_SETTING, clientSettings.getAllSettings().toArray()[1]); 46 | assertEquals(SESSION_TOKEN_SETTING, clientSettings.getAllSettings().toArray()[2]); 47 | } 48 | 49 | public void testWithSessionCredentials() throws IOException { 50 | PersonalizeClientSettings clientSettings = PersonalizeClientSettingsTestUtil.buildClientSettings(true, true, true); 51 | AWSCredentials credentials = clientSettings.getCredentials(); 52 | assertEquals(ACCESS_KEY, credentials.getAWSAccessKeyId()); 53 | assertEquals(SECRET_KEY, credentials.getAWSSecretKey()); 54 | assertTrue(credentials instanceof AWSSessionCredentials); 55 | AWSSessionCredentials sessionCredentials = (AWSSessionCredentials) credentials; 56 | assertEquals(SESSION_TOKEN, sessionCredentials.getSessionToken()); 57 | } 58 | 59 | public void testWithoutCredentials() throws IOException { 60 | PersonalizeClientSettings clientSettings = PersonalizeClientSettingsTestUtil.buildClientSettings(false, false, false); 61 | assertNull(clientSettings.getCredentials()); 62 | } 63 | 64 | public void testWithoutAccessKey() { 65 | expectThrows(SettingsException.class, () -> PersonalizeClientSettingsTestUtil.buildClientSettings(false, true, false)); 66 | expectThrows(SettingsException.class, () -> PersonalizeClientSettingsTestUtil.buildClientSettings(false, true, true)); 67 | } 68 | 69 | public void testWithoutSecretKey() { 70 | expectThrows(SettingsException.class, () -> PersonalizeClientSettingsTestUtil.buildClientSettings(true, false, false)); 71 | expectThrows(SettingsException.class, () -> PersonalizeClientSettingsTestUtil.buildClientSettings(true, false, true)); 72 | } 73 | 74 | public void testWithSessionTokenButNoCredentials() { 75 | expectThrows(SettingsException.class, () -> PersonalizeClientSettingsTestUtil.buildClientSettings(false, false, true)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/test/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/client/PersonalizeClientTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.client; 9 | 10 | import com.amazonaws.auth.AWSCredentials; 11 | import com.amazonaws.auth.AWSCredentialsProvider; 12 | import com.amazonaws.auth.AWSStaticCredentialsProvider; 13 | import com.amazonaws.auth.BasicSessionCredentials; 14 | import com.amazonaws.services.personalizeruntime.model.GetPersonalizedRankingRequest; 15 | import com.amazonaws.services.personalizeruntime.model.GetPersonalizedRankingResult; 16 | import org.mockito.Mockito; 17 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.utils.PersonalizeRuntimeTestUtil; 18 | import org.opensearch.test.OpenSearchTestCase; 19 | 20 | import java.io.IOException; 21 | 22 | import static org.mockito.ArgumentMatchers.any; 23 | 24 | public class PersonalizeClientTests extends OpenSearchTestCase { 25 | 26 | public void testCreateClient() throws IOException { 27 | AWSCredentials credentials = new BasicSessionCredentials("accessKey", "secretKey", "sessionToken"); 28 | AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials); 29 | String region = "us-west-2"; 30 | try (PersonalizeClient client = new PersonalizeClient(credentialsProvider,region)) { 31 | assertTrue(client.getPersonalizeRuntime() != null); 32 | } 33 | } 34 | 35 | public void testGetPersonalizedRanking() { 36 | PersonalizeClient client = Mockito.mock(PersonalizeClient.class); 37 | GetPersonalizedRankingRequest request = PersonalizeRuntimeTestUtil.buildGetPersonalizedRankingRequest(); 38 | Mockito.when(client.getPersonalizedRanking(any())).thenReturn(PersonalizeRuntimeTestUtil.buildGetPersonalizedRankingResult()); 39 | GetPersonalizedRankingResult result = client.getPersonalizedRanking(request); 40 | assertEquals(result.getRecommendationId(), "sampleRecommendationId"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/test/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/client/PersonalizeCredentialsProviderFactoryTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.client; 9 | 10 | import com.amazonaws.auth.AWSCredentialsProvider; 11 | import com.amazonaws.auth.AWSStaticCredentialsProvider; 12 | import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; 13 | import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; 14 | import com.amazonaws.http.IdleConnectionReaper; 15 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.utils.PersonalizeClientSettingsTestUtil; 16 | import org.opensearch.test.OpenSearchTestCase; 17 | 18 | import java.io.IOException; 19 | 20 | public class PersonalizeCredentialsProviderFactoryTests extends OpenSearchTestCase { 21 | 22 | public void testGetStaticCredentialsProviderWithoutIAMRole() throws IOException { 23 | PersonalizeClientSettings settings = 24 | PersonalizeClientSettingsTestUtil.buildClientSettings(true, true, true); 25 | 26 | AWSCredentialsProvider credentialsProvider = PersonalizeCredentialsProviderFactory.getCredentialsProvider(settings); 27 | assertEquals(credentialsProvider.getClass(), AWSStaticCredentialsProvider.class); 28 | } 29 | 30 | public void testGetDefaultCredentialsProviderWithoutIAMRole() throws IOException { 31 | PersonalizeClientSettings settings = 32 | PersonalizeClientSettingsTestUtil.buildClientSettings(false, false, false); 33 | 34 | AWSCredentialsProvider credentialsProvider = PersonalizeCredentialsProviderFactory.getCredentialsProvider(settings); 35 | assertEquals(credentialsProvider.getClass(), DefaultAWSCredentialsProviderChain.class); 36 | } 37 | 38 | public void testGetCredentialsProviderWithIAMRole() throws IOException { 39 | PersonalizeClientSettings settings = 40 | PersonalizeClientSettingsTestUtil.buildClientSettings(true, true, true); 41 | 42 | String iamRoleArn = "test-iam-role-arn"; 43 | String awsRegion = "us-west-2"; 44 | AWSCredentialsProvider credentialsProvider = PersonalizeCredentialsProviderFactory.getCredentialsProvider(settings, iamRoleArn, awsRegion); 45 | assertEquals(credentialsProvider.getClass(), STSAssumeRoleSessionCredentialsProvider.class); 46 | IdleConnectionReaper.shutdown(); 47 | } 48 | 49 | public void testGetStaticCredentialsProviderWithEmptyIAMRole() throws IOException { 50 | PersonalizeClientSettings settings = 51 | PersonalizeClientSettingsTestUtil.buildClientSettings(true, true, true); 52 | 53 | String iamRoleArn = ""; 54 | String awsRegion = "us-west-2"; 55 | AWSCredentialsProvider credentialsProvider = PersonalizeCredentialsProviderFactory.getCredentialsProvider(settings, iamRoleArn, awsRegion); 56 | assertEquals(credentialsProvider.getClass(), AWSStaticCredentialsProvider.class); 57 | } 58 | 59 | public void testGetDefaultCredentialsProviderWithEmptyIAMRole() throws IOException { 60 | PersonalizeClientSettings settings = 61 | PersonalizeClientSettingsTestUtil.buildClientSettings(false, false, false); 62 | 63 | String iamRoleArn = ""; 64 | String awsRegion = "us-west-2"; 65 | AWSCredentialsProvider credentialsProvider = PersonalizeCredentialsProviderFactory.getCredentialsProvider(settings, iamRoleArn, awsRegion); 66 | assertEquals(credentialsProvider.getClass(), DefaultAWSCredentialsProviderChain.class); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/test/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/configuration/PersonalizeIntelligentRankerConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.configuration; 9 | 10 | import org.opensearch.test.OpenSearchTestCase; 11 | 12 | public class PersonalizeIntelligentRankerConfigurationTests extends OpenSearchTestCase { 13 | 14 | public void createConfigurationTest() { 15 | String personalizeCampaign = "arn:aws:personalize:us-west-2:000000000000:campaign/test-campaign"; 16 | String iamRoleArn = "sampleRoleArn"; 17 | String recipe = "sample-personalize-recipe"; 18 | String itemIdField = "ITEM_ID"; 19 | String region = "us-west-2"; 20 | double weight = 0.25; 21 | 22 | PersonalizeIntelligentRankerConfiguration config = 23 | new PersonalizeIntelligentRankerConfiguration(personalizeCampaign, iamRoleArn, recipe, itemIdField, region, weight); 24 | 25 | assertEquals(config.getPersonalizeCampaign(), personalizeCampaign); 26 | assertEquals(config.getIamRoleArn(), iamRoleArn); 27 | assertEquals(config.getRecipe(), recipe); 28 | assertEquals(config.getItemIdField(), itemIdField); 29 | assertEquals(config.getRegion(), region); 30 | assertEquals(config.getWeight(), weight, 0.0); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/test/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/ranker/PersonalizeRankerFactoryTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.ranker; 9 | 10 | import org.mockito.Mockito; 11 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.client.PersonalizeClient; 12 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.configuration.PersonalizeIntelligentRankerConfiguration; 13 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.reranker.PersonalizedRanker; 14 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.reranker.PersonalizedRankerFactory; 15 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.reranker.impl.AmazonPersonalizedRankerImpl; 16 | import org.opensearch.test.OpenSearchTestCase; 17 | 18 | import static org.opensearch.search.relevance.transformer.personalizeintelligentranking.configuration.Constants.AMAZON_PERSONALIZED_RANKING_RECIPE_NAME; 19 | 20 | public class PersonalizeRankerFactoryTests extends OpenSearchTestCase { 21 | 22 | private String personalizeCampaign = "arn:aws:personalize:us-west-2:000000000000:campaign/test-campaign"; 23 | private String iamRoleArn = "sampleRoleArn"; 24 | private String itemIdField = "ITEM_ID"; 25 | private String region = "us-west-2"; 26 | private double weight = 0.25; 27 | 28 | public void testGetPersonalizeRankerForPersonalizedRankingRecipe() { 29 | PersonalizeClient client = Mockito.mock(PersonalizeClient.class); 30 | PersonalizeIntelligentRankerConfiguration rankerConfig = 31 | new PersonalizeIntelligentRankerConfiguration(personalizeCampaign, iamRoleArn, AMAZON_PERSONALIZED_RANKING_RECIPE_NAME, itemIdField, region, weight); 32 | 33 | PersonalizedRankerFactory factory = new PersonalizedRankerFactory(); 34 | PersonalizedRanker ranker = factory.getPersonalizedRanker(rankerConfig, client); 35 | assertEquals(ranker.getClass(), AmazonPersonalizedRankerImpl.class); 36 | } 37 | 38 | public void testGetPersonalizeRankerForUnknownRecipe() { 39 | PersonalizeClient client = Mockito.mock(PersonalizeClient.class); 40 | PersonalizeIntelligentRankerConfiguration rankerConfig = 41 | new PersonalizeIntelligentRankerConfiguration(personalizeCampaign, iamRoleArn, "sample-recipe", itemIdField, region, weight); 42 | 43 | PersonalizedRankerFactory factory = new PersonalizedRankerFactory(); 44 | PersonalizedRanker ranker = factory.getPersonalizedRanker(rankerConfig, client); 45 | assertNull(ranker); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/test/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/requestparameter/PersonalizeRequestParameterUtilTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | * 8 | */ 9 | 10 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.requestparameter; 11 | 12 | import org.opensearch.action.search.SearchRequest; 13 | import org.opensearch.search.builder.SearchSourceBuilder; 14 | import org.opensearch.test.OpenSearchTestCase; 15 | 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | public class PersonalizeRequestParameterUtilTests extends OpenSearchTestCase { 21 | 22 | public void testExtractParameters() { 23 | PersonalizeRequestParameters expected = new PersonalizeRequestParameters("user_1", new HashMap<>()); 24 | PersonalizeRequestParametersExtBuilder extBuilder = new PersonalizeRequestParametersExtBuilder(); 25 | extBuilder.setRequestParameters(expected); 26 | SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource() 27 | .ext(List.of(extBuilder)); 28 | SearchRequest request = new SearchRequest("my_index").source(sourceBuilder); 29 | PersonalizeRequestParameters actual = PersonalizeRequestParameterUtil.getPersonalizeRequestParameters(request); 30 | assertEquals(expected, actual); 31 | } 32 | 33 | public void testExtractParametersWithContext() { 34 | Map context = new HashMap<>(); 35 | context.put("contextKey", "contextValue"); 36 | PersonalizeRequestParameters expected = new PersonalizeRequestParameters("user_1", context); 37 | PersonalizeRequestParametersExtBuilder extBuilder = new PersonalizeRequestParametersExtBuilder(); 38 | extBuilder.setRequestParameters(expected); 39 | SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource() 40 | .ext(List.of(extBuilder)); 41 | SearchRequest request = new SearchRequest("my_index").source(sourceBuilder); 42 | PersonalizeRequestParameters actual = PersonalizeRequestParameterUtil.getPersonalizeRequestParameters(request); 43 | assertEquals(expected, actual); 44 | } 45 | 46 | public void testPersonalizeRequestParametersEquals() { 47 | Map notExpectedContext = new HashMap<>(); 48 | notExpectedContext.put("contextKey", "contextValue"); 49 | PersonalizeRequestParameters notExpected = new PersonalizeRequestParameters("user_1", notExpectedContext); 50 | 51 | Map expectedContext = new HashMap<>(); 52 | expectedContext.put("contextKey2", "contextValue2"); 53 | PersonalizeRequestParameters expected = new PersonalizeRequestParameters("user_1", expectedContext); 54 | PersonalizeRequestParametersExtBuilder extBuilder = new PersonalizeRequestParametersExtBuilder(); 55 | extBuilder.setRequestParameters(expected); 56 | SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource() 57 | .ext(List.of(extBuilder)); 58 | SearchRequest request = new SearchRequest("my_index").source(sourceBuilder); 59 | PersonalizeRequestParameters actual = PersonalizeRequestParameterUtil.getPersonalizeRequestParameters(request); 60 | assertNotEquals(notExpected, actual); 61 | } 62 | 63 | public void testPersonalizeRequestParametersContextMapDifferentSize() { 64 | Map notExpectedContext = new HashMap<>(); 65 | notExpectedContext.put("contextKey", "contextValue"); 66 | PersonalizeRequestParameters notExpected = new PersonalizeRequestParameters("user_1", notExpectedContext); 67 | 68 | Map expectedContext = new HashMap<>(); 69 | expectedContext.put("contextKey2", "contextValue2"); 70 | expectedContext.put("contextKey22", "contextValue22"); 71 | PersonalizeRequestParameters expected = new PersonalizeRequestParameters("user_1", expectedContext); 72 | PersonalizeRequestParametersExtBuilder extBuilder = new PersonalizeRequestParametersExtBuilder(); 73 | extBuilder.setRequestParameters(expected); 74 | SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource() 75 | .ext(List.of(extBuilder)); 76 | SearchRequest request = new SearchRequest("my_index").source(sourceBuilder); 77 | PersonalizeRequestParameters actual = PersonalizeRequestParameterUtil.getPersonalizeRequestParameters(request); 78 | assertNotEquals(notExpected, actual); 79 | } 80 | 81 | public void testPersonalizeRequestParametersUserIdDiffers() { 82 | Map notExpectedContext = new HashMap<>(); 83 | notExpectedContext.put("contextKey", "contextValue"); 84 | PersonalizeRequestParameters notExpected = new PersonalizeRequestParameters("user_1", notExpectedContext); 85 | 86 | Map expectedContext = new HashMap<>(); 87 | expectedContext.put("contextKey", "contextValue"); 88 | PersonalizeRequestParameters expected = new PersonalizeRequestParameters("user_2", expectedContext); 89 | PersonalizeRequestParametersExtBuilder extBuilder = new PersonalizeRequestParametersExtBuilder(); 90 | extBuilder.setRequestParameters(expected); 91 | SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource() 92 | .ext(List.of(extBuilder)); 93 | SearchRequest request = new SearchRequest("my_index").source(sourceBuilder); 94 | PersonalizeRequestParameters actual = PersonalizeRequestParameterUtil.getPersonalizeRequestParameters(request); 95 | assertNotEquals(notExpected, actual); 96 | } 97 | } -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/test/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/requestparameter/PersonalizeRequestParametersExtBuilderTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.requestparameter; 9 | 10 | import org.opensearch.core.common.bytes.BytesReference; 11 | import org.opensearch.common.io.stream.BytesStreamOutput; 12 | import org.opensearch.common.xcontent.XContentHelper; 13 | import org.opensearch.common.xcontent.XContentType; 14 | import org.opensearch.core.xcontent.XContentParser; 15 | import org.opensearch.test.OpenSearchTestCase; 16 | 17 | import java.io.IOException; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | public class PersonalizeRequestParametersExtBuilderTests extends OpenSearchTestCase { 22 | 23 | public void testXContentRoundTrip() throws IOException { 24 | Map context = new HashMap<>(); 25 | context.put("contextKey", "contextValue"); 26 | PersonalizeRequestParameters requestParameters = new PersonalizeRequestParameters("28", context); 27 | PersonalizeRequestParametersExtBuilder personalizeExtBuilder = new PersonalizeRequestParametersExtBuilder(); 28 | personalizeExtBuilder.setRequestParameters(requestParameters); 29 | XContentType xContentType = randomFrom(XContentType.values()); 30 | BytesReference serialized = XContentHelper.toXContent(personalizeExtBuilder, xContentType, true); 31 | 32 | XContentParser parser = createParser(xContentType.xContent(), serialized); 33 | 34 | PersonalizeRequestParametersExtBuilder deserialized = 35 | PersonalizeRequestParametersExtBuilder.parse(parser); 36 | 37 | assertEquals(personalizeExtBuilder, deserialized); 38 | } 39 | 40 | public void testStreamRoundTrip() throws IOException { 41 | PersonalizeRequestParameters requestParameters = new PersonalizeRequestParameters(); 42 | requestParameters.setUserId("28"); 43 | requestParameters.setContext(new HashMap<>()); 44 | PersonalizeRequestParametersExtBuilder personalizeExtBuilder = new PersonalizeRequestParametersExtBuilder(); 45 | personalizeExtBuilder.setRequestParameters(requestParameters); 46 | BytesStreamOutput bytesStreamOutput = new BytesStreamOutput(); 47 | personalizeExtBuilder.writeTo(bytesStreamOutput); 48 | 49 | PersonalizeRequestParametersExtBuilder deserialized = 50 | new PersonalizeRequestParametersExtBuilder(bytesStreamOutput.bytes().streamInput()); 51 | assertEquals(personalizeExtBuilder, deserialized); 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/test/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/utils/PersonalizeClientSettingsTestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.utils; 9 | 10 | import org.opensearch.common.settings.MockSecureSettings; 11 | import org.opensearch.common.settings.Settings; 12 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.client.PersonalizeClientSettings; 13 | 14 | import java.io.IOException; 15 | 16 | public class PersonalizeClientSettingsTestUtil { 17 | public static final String ACCESS_KEY = "my-access-key"; 18 | public static final String SECRET_KEY = "my-secret-key"; 19 | public static final String SESSION_TOKEN = "session-token"; 20 | 21 | public static PersonalizeClientSettings buildClientSettings(boolean withAccessKey, boolean withSecretKey, 22 | boolean withSessionToken) throws IOException { 23 | try (MockSecureSettings secureSettings = new MockSecureSettings()) { 24 | if (withAccessKey) { 25 | secureSettings.setString(PersonalizeClientSettings.ACCESS_KEY_SETTING.getKey(), ACCESS_KEY); 26 | } 27 | if (withSecretKey) { 28 | secureSettings.setString(PersonalizeClientSettings.SECRET_KEY_SETTING.getKey(), SECRET_KEY); 29 | } 30 | if (withSessionToken) { 31 | secureSettings.setString(PersonalizeClientSettings.SESSION_TOKEN_SETTING.getKey(), SESSION_TOKEN); 32 | } 33 | Settings settings = Settings.builder() 34 | .setSecureSettings(secureSettings) 35 | .build(); 36 | return PersonalizeClientSettings.getClientSettings(settings); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/test/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/utils/PersonalizeRuntimeTestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.utils; 9 | 10 | import com.amazonaws.services.personalizeruntime.model.GetPersonalizedRankingRequest; 11 | import com.amazonaws.services.personalizeruntime.model.GetPersonalizedRankingResult; 12 | import com.amazonaws.services.personalizeruntime.model.PredictedItem; 13 | import org.mockito.Mockito; 14 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.client.PersonalizeClient; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.function.Function; 19 | 20 | import static org.mockito.ArgumentMatchers.any; 21 | 22 | public class PersonalizeRuntimeTestUtil { 23 | 24 | public static GetPersonalizedRankingRequest buildGetPersonalizedRankingRequest() { 25 | GetPersonalizedRankingRequest request = new GetPersonalizedRankingRequest() 26 | .withUserId("sampleUserId") 27 | .withInputList(new ArrayList()) 28 | .withCampaignArn("sampleCampaign"); 29 | return request; 30 | } 31 | 32 | public static GetPersonalizedRankingResult buildGetPersonalizedRankingResult() { 33 | List predictedItems = new ArrayList<>(); 34 | GetPersonalizedRankingResult result = new GetPersonalizedRankingResult() 35 | .withPersonalizedRanking(predictedItems) 36 | .withRecommendationId("sampleRecommendationId"); 37 | return result; 38 | } 39 | 40 | public static GetPersonalizedRankingResult buildGetPersonalizedRankingResult(int numOfHits) { 41 | List predictedItems = new ArrayList<>(); 42 | for(int i = numOfHits; i >= 1; i--){ 43 | PredictedItem predictedItem = new PredictedItem(). 44 | withScore((double) i/10). 45 | withItemId(String.valueOf(i-1)); 46 | predictedItems.add(predictedItem); 47 | } 48 | GetPersonalizedRankingResult result = new GetPersonalizedRankingResult() 49 | .withPersonalizedRanking(predictedItems) 50 | .withRecommendationId("sampleRecommendationId"); 51 | return result; 52 | } 53 | 54 | public static ArrayList expectedRankedItemIdsForGivenWeight(int numOfHits, int weight){ 55 | ArrayList expectedRankedItemIds = new ArrayList<>(); 56 | if (weight == 0) { 57 | for (int i = 0; i < numOfHits; i++) { 58 | expectedRankedItemIds.add(String.valueOf(i)); 59 | } 60 | } else if (weight == 1){ 61 | for(int i = numOfHits; i >= 1; i--){ 62 | expectedRankedItemIds.add(String.valueOf(i-1)); 63 | } 64 | } 65 | return expectedRankedItemIds; 66 | } 67 | 68 | public static PersonalizeClient buildMockPersonalizeClient() { 69 | return buildMockPersonalizeClient(r -> buildGetPersonalizedRankingResult(10)); 70 | } 71 | 72 | private static PersonalizeClient buildMockPersonalizeClient( 73 | Function mockGetPersonalizedRankingImpl) { 74 | PersonalizeClient personalizeClient = Mockito.mock(PersonalizeClient.class); 75 | Mockito.doAnswer(invocation -> { 76 | GetPersonalizedRankingRequest request = invocation.getArgument(0); 77 | return mockGetPersonalizedRankingImpl.apply(request); 78 | }).when(personalizeClient).getPersonalizedRanking(any(GetPersonalizedRankingRequest.class)); 79 | return personalizeClient; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/test/java/org/opensearch/search/relevance/transformer/personalizeintelligentranking/utils/SearchTestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance.transformer.personalizeintelligentranking.utils; 9 | 10 | import org.apache.lucene.search.TotalHits; 11 | import org.opensearch.action.search.SearchRequest; 12 | import org.opensearch.core.common.bytes.BytesReference; 13 | import org.opensearch.common.xcontent.json.JsonXContent; 14 | import org.opensearch.core.xcontent.XContentBuilder; 15 | import org.opensearch.search.SearchHit; 16 | import org.opensearch.search.SearchHits; 17 | import org.opensearch.search.builder.SearchSourceBuilder; 18 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.requestparameter.PersonalizeRequestParameters; 19 | import org.opensearch.search.relevance.transformer.personalizeintelligentranking.requestparameter.PersonalizeRequestParametersExtBuilder; 20 | 21 | import java.io.IOException; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | public class SearchTestUtil { 26 | public static SearchHits getSampleSearchHitsForPersonalize(int numHits) throws IOException { 27 | SearchHit[] hitsArray = new SearchHit[numHits]; 28 | float maxScore = 0.0f; 29 | for (int i = 0; i < numHits; i++) { 30 | XContentBuilder sourceContent = JsonXContent.contentBuilder() 31 | .startObject() 32 | .field("ITEM_ID", String.valueOf(i)) 33 | .field("body", "Body text for document number " + i) 34 | .field("title", "This is the title for document " + i) 35 | .endObject(); 36 | hitsArray[i] = new SearchHit(i, String.valueOf(i), Map.of(), Map.of()); 37 | float score = (float)(numHits-i)/10; 38 | maxScore = Math.max(score, maxScore); 39 | hitsArray[i].score(score); 40 | hitsArray[i].sourceRef(BytesReference.bytes(sourceContent)); 41 | } 42 | SearchHits searchHits = new SearchHits(hitsArray, new TotalHits(numHits, TotalHits.Relation.EQUAL_TO), maxScore); 43 | return searchHits; 44 | } 45 | 46 | public static SearchRequest createSearchRequestWithPersonalizeRequest(PersonalizeRequestParameters personalizeRequestParams) { 47 | PersonalizeRequestParametersExtBuilder extBuilder = new PersonalizeRequestParametersExtBuilder(); 48 | extBuilder.setRequestParameters(personalizeRequestParams); 49 | SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource() 50 | .ext(List.of(extBuilder)); 51 | 52 | SearchRequest searchRequest = new SearchRequest().source(sourceBuilder); 53 | return searchRequest; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/yamlRestTest/java/org/opensearch/search/relevance/AmazonPersonalizeRankingClientYamlTestSuiteIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | * 4 | * The OpenSearch Contributors require contributions made to 5 | * this file be licensed under the Apache-2.0 license or a 6 | * compatible open source license. 7 | */ 8 | package org.opensearch.search.relevance; 9 | 10 | import com.carrotsearch.randomizedtesting.annotations.Name; 11 | import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; 12 | import org.opensearch.test.rest.yaml.ClientYamlTestCandidate; 13 | import org.opensearch.test.rest.yaml.OpenSearchClientYamlSuiteTestCase; 14 | 15 | 16 | public class AmazonPersonalizeRankingClientYamlTestSuiteIT extends OpenSearchClientYamlSuiteTestCase { 17 | 18 | public AmazonPersonalizeRankingClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { 19 | super(testCandidate); 20 | } 21 | 22 | @ParametersFactory 23 | public static Iterable parameters() throws Exception { 24 | return OpenSearchClientYamlSuiteTestCase.createParameters(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /amazon-personalize-ranking/src/yamlRestTest/resources/rest-api-spec/test/10_basic.yml: -------------------------------------------------------------------------------- 1 | "Test that the plugin is loaded in OpenSearch": 2 | - do: 3 | cat.plugins: 4 | local: true 5 | h: component 6 | 7 | - match: 8 | $body: /^opensearch-amazon-personalize-ranking-\d+.\d+.\d+.\d+\n$/ 9 | 10 | - do: 11 | indices.create: 12 | index: test 13 | 14 | - do: 15 | search: 16 | index: test 17 | body: { } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | isSnapshot = "true" == System.getProperty("build.snapshot", "true") 3 | opensearch_version = System.getProperty("opensearch.version", "3.0.0") 4 | plugin_version = opensearch_version 5 | if (isSnapshot) { 6 | opensearch_version += "-SNAPSHOT" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/search-processor/ad6f945b1c5802316cc916ec1836454ae3db2605/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=7a00d51fb93147819aab76024feece20b6b84e420694101f276be952e08bef03 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /release-notes/search-processor.release-notes-2.4.0.md: -------------------------------------------------------------------------------- 1 | ## Version 2.4.0 Release Notes 2 | 3 | This is the initial release of the search-processor plugin for OpenSearch 2.4.0. 4 | This plugin serves as an extension point to customize search behavior by preprocessing search requests and post-processing search results. 5 | In this first release, the only implementation is a search result post-processor (reranker) that passes results to AWS Kendra Intelligent Ranking. 6 | More implementations and integrations will be added in future releases. Contributions are encouraged! 7 | 8 | ### What's Changed 9 | 10 | - Search/relevancy ([#13](https://github.com/opensearch-project/search-processor/pull/13)) 11 | - adding MAINTAINERS.md ([#16](https://github.com/opensearch-project/search-processor/pull/16)) 12 | - Initial code for plugin based on opensearch-plugin-template-java ([#3](https://github.com/opensearch-project/search-processor/pull/3)) 13 | - Merge all changes to 2.x branch before cutting 2.4 branch ([#34](https://github.com/opensearch-project/search-processor/pull/34)) 14 | 15 | ### New Contributors 16 | - @YANG-DB made their first contribution in ([#13](https://github.com/opensearch-project/search-processor/pull/13)) 17 | - @kevinawskendra made their first contribution in ([#3](https://github.com/opensearch-project/search-processor/pull/3)) 18 | - @mahitamahesh made their first contribution in ([#23](https://github.com/opensearch-project/search-processor/pull/23)) 19 | 20 | Full Changelog: https://github.com/opensearch-project/search-relevance/commits/2.4.0 -------------------------------------------------------------------------------- /release-notes/search-processor.release-notes-2.5.0.md: -------------------------------------------------------------------------------- 1 | Compatible with OpenSearch 2.5.0. 2 | 3 | ### Features 4 | 5 | * Add a Docker quickstart script for demo / test ([#25](https://github.com/opensearch-project/search-processor/pull/25)) 6 | * Update log statements in quickstart helper script ([#79](https://github.com/opensearch-project/search-processor/pull/79)) 7 | 8 | ### Enhancements 9 | 10 | * Bug fixes and refactoring ([#38](https://github.com/opensearch-project/search-processor/pull/38)) 11 | 12 | ### Infrastructure 13 | 14 | * Configure Mend for GitHub.com ([#37](https://github.com/opensearch-project/search-processor/pull/37)) 15 | * Fix codecov after repo renaming ([#41](https://github.com/opensearch-project/search-processor/pull/41)) 16 | * stripped down version of pr stats for only this repository ([#56](https://github.com/opensearch-project/search-processor/pull/56)) 17 | * Fix permissions for PR Stats action ([#67](https://github.com/opensearch-project/search-processor/pull/67)) 18 | 19 | ### Documentation 20 | 21 | * Added DEVELOPER_GUIDE.md and html jacoco output ([#35](https://github.com/opensearch-project/search-processor/pull/35)) 22 | * Use short-form MAINTAINERS.md. ([#39](https://github.com/opensearch-project/search-processor/pull/39)) 23 | * Maintainer update and PR stats github action ([#43](https://github.com/opensearch-project/search-processor/pull/43)) 24 | * Updating README.md to talk more about Search Query & Request Transfor… ([#58](https://github.com/opensearch-project/search-processor/pull/58)) 25 | * minor updates to README.md; removed dead link to credits and an unnec… ([#65](https://github.com/opensearch-project/search-processor/pull/65)) 26 | * Updated MAINTAINERS.md format. ([#77](https://github.com/opensearch-project/search-processor/pull/77)) 27 | * adding Mingshi Liu as a maintainer to the repo ([#86](https://github.com/opensearch-project/search-processor/pull/86)) 28 | * Add release-notes folder and 2.4.0 md file ([#89](https://github.com/opensearch-project/search-processor/pull/89)) 29 | * Add release note for 2.5.0 ([#95](https://github.com/opensearch-project/search-processor/pull/95)) 30 | 31 | ### Maintenance 32 | * Remove DCO config and update PR template link ([#51](https://github.com/opensearch-project/search-processor/pull/51)) 33 | * Add config rule for auto categorizing labels ([#84](https://github.com/opensearch-project/search-processor/pull/84)) 34 | * Increment from 2.4 to 2.5 and bump jackson from 2.14.0 to 2.14.1 ([#90](https://github.com/opensearch-project/search-processor/pull/90)) 35 | 36 | **Full Changelog**: https://github.com/opensearch-project/search-processor/compare/2.4.0...2.5.0 -------------------------------------------------------------------------------- /release-notes/search-processor.release-notes-2.6.0.md: -------------------------------------------------------------------------------- 1 | ## Version 2.6.0 Release Notes 2 | 3 | ### Enhancements 4 | * Add more test coverage [#100](https://github.com/opensearch-project/search-processor/pull/100) 5 | 6 | ### Infrastructure 7 | * Update artifact name [#106](https://github.com/opensearch-project/search-processor/pull/106) 8 | 9 | ### Maintenance 10 | * Bumping plugin version to `2.6.0` and `jackson-core` version to `2.14.2` [#110](https://github.com/opensearch-project/search-processor/pull/110) 11 | 12 | ### Documentation 13 | * Adding `2.6.0` release notes [#110](https://github.com/opensearch-project/search-processor/pull/110) 14 | -------------------------------------------------------------------------------- /release-notes/search-processor.release-notes-2.7.0.md: -------------------------------------------------------------------------------- 1 | ## Version 2.7.0 Release Notes 2 | 3 | ### Enhancements 4 | * Improve test coverage [#99](https://github.com/opensearch-project/search-processor/pull/99) 5 | 6 | ### Infrastructure 7 | * Updating build.gradle to use snapshot by default [#124](https://github.com/opensearch-project/search-processor/pull/124) 8 | * Accommodate changes to XContent classes [#125](https://github.com/opensearch-project/search-processor/pull/125) 9 | 10 | ### Documentation 11 | * Prepping for 2.7.0 release [#130](https://github.com/opensearch-project/search-processor/pull/130) 12 | -------------------------------------------------------------------------------- /release-notes/search-processor.release-notes-2.8.0.md: -------------------------------------------------------------------------------- 1 | 2 | ## Version 2.8.0.0 Release Notes 3 | 4 | Compatible with OpenSearch 2.8.0 5 | 6 | 7 | ### Features 8 | * Add KendraRankingResponseProcessor ([#137](https://github.com/opensearch-project/search-processor/pull/137)) 9 | * Personalized intelligent ranking for open search requests ([#138](https://github.com/opensearch-project/search-processor/pull/138)) -------------------------------------------------------------------------------- /release-notes/search-processor.release-notes-2.9.0.md: -------------------------------------------------------------------------------- 1 | ## Version 2.9.0.0 Release Notes 2 | 3 | Compatible with OpenSearch 2.9.0 4 | 5 | 6 | ### Enhancements 7 | * Support contextual metadata to use when getting personalized reranking ([#144](https://github.com/opensearch-project/search-processor/pull/144)) 8 | * Scoring logic for reranking open search hits based on personalize campaign response ([#147](https://github.com/opensearch-project/search-processor/pull/147)) 9 | * Bring Search Pipeline Update and Add IgnoreFailure and PipelineContext To Each Processors ([#152](https://github.com/opensearch-project/search-processor/pull/152)) 10 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.5.1/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = 'search-processor' 11 | include 'amazon-kendra-intelligent-ranking' 12 | include 'amazon-personalize-ranking' 13 | --------------------------------------------------------------------------------