From 9e99933a27a7f5a9c152e797b2d37e3be5bf62d7 Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Tue, 28 May 2024 17:28:04 +0200 Subject: [PATCH] Python: new pre-commit actions and pre-commit as GHA (#6376) ### Motivation and Context - Added static code checking to pre-commit (check-ast and nbqa-check-ast) - Added Bandit security checking to pre-commit - Added pre-commit step to python-lint workflow, if it works, can delete seperate mypy, ruff and black tests ### Description ### Contribution Checklist - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone :smile: --- .github/workflows/python-lint.yml | 55 +----- .github/workflows/python-test-coverage.yml | 6 +- .pre-commit-config.yaml | 32 +++- python/.vscode/extensions.json | 3 +- python/.vscode/settings.json | 1 - python/.vscode/tasks.json | 3 +- python/poetry.lock | 143 +++++--------- python/pyproject.toml | 36 +++- ...ython_code_interpreter_function_calling.py | 6 +- .../plugins/azure_python_code_interpreter.py | 6 +- .../plugins/openai_plugin_azure_key_vault.py | 4 +- .../resources/email_plugin/native_function.py | 3 +- .../google_palm_text_completion.py | 4 +- .../bookings_plugin/bookings_plugin.py | 1 - .../03-prompt-function-inline.ipynb | 5 +- python/samples/getting_started/services.py | 12 +- python/samples/learn_resources/ai_services.py | 5 +- .../plugins/MathPlugin/native_function.py | 3 +- .../learn_resources/service_configurator.py | 5 +- .../ai/chat_completion_client_base.py | 38 ++-- .../ai/embeddings/embedding_generator_base.py | 7 + .../gp_prompt_execution_settings.py | 1 + .../services/gp_chat_completion.py | 91 +++++---- .../services/gp_text_completion.py | 28 +-- .../google_palm/services/gp_text_embedding.py | 21 +-- .../settings/google_palm_settings.py | 5 +- .../hf_prompt_execution_settings.py | 2 + .../services/hf_text_completion.py | 42 ++--- .../services/hf_text_embedding.py | 27 ++- .../ollama/services/ollama_chat_completion.py | 62 +++--- .../ollama/services/ollama_text_completion.py | 32 ++-- .../ollama/services/ollama_text_embedding.py | 24 ++- .../connectors/ai/ollama/utils.py | 3 + .../exceptions/content_filter_ai_exception.py | 16 +- .../azure_chat_prompt_execution_settings.py | 1 + .../open_ai_prompt_execution_settings.py | 2 +- .../open_ai/services/azure_chat_completion.py | 33 ++-- .../ai/open_ai/services/azure_config_base.py | 29 +-- .../open_ai/services/azure_text_completion.py | 37 ++-- .../open_ai/services/azure_text_embedding.py | 24 +-- .../services/open_ai_chat_completion.py | 31 ++- .../services/open_ai_chat_completion_base.py | 31 +-- .../open_ai/services/open_ai_config_base.py | 18 +- .../ai/open_ai/services/open_ai_handler.py | 26 ++- .../services/open_ai_text_completion.py | 34 ++-- .../services/open_ai_text_completion_base.py | 20 +- .../services/open_ai_text_embedding.py | 37 ++-- .../services/open_ai_text_embedding_base.py | 20 +- .../settings/azure_open_ai_settings.py | 5 +- .../ai/open_ai/settings/open_ai_settings.py | 5 +- .../ai/prompt_execution_settings.py | 16 +- .../ai/text_completion_client_base.py | 24 ++- .../connectors/memory/astradb/astra_client.py | 13 ++ .../memory/astradb/astradb_memory_store.py | 155 ++++++++------- .../memory/astradb/astradb_settings.py | 4 +- .../connectors/memory/astradb/utils.py | 11 +- .../azure_ai_search_settings.py | 10 +- .../azure_cognitive_search_memory_store.py | 124 ++++++------ .../memory/azure_cognitive_search/utils.py | 45 ++--- .../azure_cosmos_db_memory_store.py | 130 ++++++------- .../azure_cosmos_db_store_api.py | 105 +++++++++++ .../azure_cosmosdb/azure_cosmosdb_settings.py | 6 +- .../memory/azure_cosmosdb/cosmosdb_utils.py | 9 +- .../azure_cosmosdb/mongo_vcore_store_api.py | 23 ++- .../azure_cosmosdb_no_sql_memory_store.py | 31 ++- .../memory/chroma/chroma_memory_store.py | 151 +++++++-------- .../connectors/memory/chroma/utils.py | 29 +-- .../connectors/memory/memory_settings_base.py | 3 + .../memory/milvus/milvus_memory_store.py | 17 +- .../mongodb_atlas_memory_store.py | 116 ++++++------ .../mongodb_atlas/mongodb_atlas_settings.py | 6 +- .../connectors/memory/mongodb_atlas/utils.py | 16 +- .../memory/pinecone/pinecone_memory_store.py | 134 ++++++------- .../memory/pinecone/pinecone_settings.py | 6 +- .../connectors/memory/pinecone/utils.py | 8 +- .../memory/postgres/postgres_memory_store.py | 113 ++++++----- .../memory/postgres/postgres_settings.py | 6 +- .../memory/qdrant/qdrant_memory_store.py | 79 +++----- .../memory/redis/redis_memory_store.py | 178 +++++++++--------- .../connectors/memory/redis/redis_settings.py | 10 +- .../connectors/memory/redis/utils.py | 23 +-- .../memory/usearch/usearch_memory_store.py | 14 +- .../memory/weaviate/weaviate_memory_store.py | 38 ++-- .../memory/weaviate/weaviate_settings.py | 15 +- .../connectors/openai_plugin/openai_utils.py | 1 - .../models/rest_api_operation.py | 17 +- .../rest_api_operation_expected_response.py | 1 + .../models/rest_api_operation_parameter.py | 1 + .../models/rest_api_operation_payload.py | 1 + .../rest_api_operation_payload_property.py | 1 + .../models/rest_api_operation_run_options.py | 1 + .../openapi_plugin/models/rest_api_uri.py | 3 +- .../openapi_function_execution_parameters.py | 2 +- .../openapi_plugin/openapi_parser.py | 6 +- .../openapi_plugin/openapi_runner.py | 11 +- .../search_engine/bing_connector.py | 23 +-- .../search_engine/bing_connector_settings.py | 5 +- .../connectors/search_engine/connector.py | 5 +- .../search_engine/google_connector.py | 15 +- .../semantic_kernel/connectors/telemetry.py | 4 +- .../connectors/utils/document_loader.py | 2 +- .../semantic_kernel/contents/author_role.py | 2 +- .../semantic_kernel/contents/chat_history.py | 48 ++--- .../contents/chat_message_content.py | 49 ++--- .../semantic_kernel/contents/finish_reason.py | 2 +- .../contents/function_call_content.py | 3 +- .../contents/function_result_content.py | 3 +- .../contents/kernel_content.py | 4 + .../streaming_chat_message_content.py | 48 ++--- .../contents/streaming_content_mixin.py | 3 +- .../contents/streaming_text_content.py | 1 + .../semantic_kernel/contents/text_content.py | 3 +- .../conversation_summary_plugin.py | 10 +- .../core_plugins/http_plugin.py | 50 +++-- .../core_plugins/math_plugin.py | 9 +- .../sessions_python_plugin.py | 21 ++- .../sessions_python_settings.py | 3 + .../core_plugins/text_memory_plugin.py | 30 ++- .../core_plugins/text_plugin.py | 24 +-- .../core_plugins/time_plugin.py | 72 +++---- .../core_plugins/wait_plugin.py | 6 +- .../core_plugins/web_search_engine_plugin.py | 16 +- .../exceptions/function_exceptions.py | 1 + .../exceptions/template_engine_exceptions.py | 4 + .../filters/kernel_filters_extension.py | 1 + .../functions/function_result.py | 4 +- .../functions/kernel_arguments.py | 11 +- .../functions/kernel_function.py | 28 +-- .../functions/kernel_function_decorator.py | 11 +- .../functions/kernel_function_extension.py | 28 ++- .../functions/kernel_function_from_method.py | 5 +- .../functions/kernel_function_from_prompt.py | 3 +- .../functions/kernel_function_metadata.py | 6 +- .../functions/kernel_parameter_metadata.py | 3 +- .../functions/kernel_plugin.py | 31 +-- .../functions/prompt_rendering_result.py | 3 +- python/semantic_kernel/kernel.py | 32 ++-- .../memory/memory_query_result.py | 28 +-- .../semantic_kernel/memory/memory_record.py | 57 +++--- .../memory/memory_store_base.py | 130 +++++++------ python/semantic_kernel/memory/null_memory.py | 11 +- .../memory/semantic_text_memory.py | 66 +++---- .../memory/semantic_text_memory_base.py | 65 ++++--- .../memory/volatile_memory_store.py | 98 +++++----- .../function_calling_stepwise_planner.py | 14 +- ...nction_calling_stepwise_planner_options.py | 1 + ...unction_calling_stepwise_planner_result.py | 6 +- python/semantic_kernel/planners/plan.py | 37 +++- .../planners/planner_extensions.py | 4 + .../planners/planner_options.py | 2 +- .../sequential_planner/sequential_planner.py | 6 +- .../sequential_planner_config.py | 1 + .../sequential_planner_extensions.py | 5 + .../sequential_planner_parser.py | 2 + .../handlebars_prompt_template.py | 5 +- .../prompt_template/jinja2_prompt_template.py | 8 +- .../prompt_template/kernel_prompt_template.py | 17 +- .../prompt_template/prompt_template_base.py | 4 +- .../prompt_template/prompt_template_config.py | 4 +- .../reliability/pass_through_without_retry.py | 6 +- .../reliability/retry_mechanism_base.py | 6 +- .../schema/kernel_json_schema_builder.py | 1 - .../services/ai_service_selector.py | 5 +- .../services/kernel_services_extension.py | 7 + .../template_engine/blocks/block.py | 1 + .../blocks/function_id_block.py | 1 + .../template_engine/blocks/named_arg_block.py | 1 + .../template_engine/blocks/text_block.py | 7 +- .../template_engine/blocks/val_block.py | 1 + .../template_engine/blocks/var_block.py | 4 +- .../template_engine/code_tokenizer.py | 1 + .../protocols/code_renderer.py | 7 +- .../protocols/text_renderer.py | 7 +- .../template_engine/template_tokenizer.py | 7 +- .../text/function_extension.py | 4 +- python/semantic_kernel/text/text_chunker.py | 46 ++--- .../utils/experimental_decorator.py | 2 + python/semantic_kernel/utils/logging.py | 2 +- python/semantic_kernel/utils/naming.py | 6 +- python/setup_dev.sh | 7 + .../TestNativePlugin/custom_class.py | 7 +- .../TestNativePluginArgs/class_args.py | 7 +- .../native_function.py | 3 +- .../TestMixedPlugin/native_function.py | 7 +- python/tests/conftest.py | 4 +- .../connectors/memory/test_usearch.py | 1 - .../cross_language/test_cross_language.py | 21 +-- .../test_kernel_function_decorators.py | 4 +- .../tests/unit/services/test_service_utils.py | 2 +- python/tests/unit/text/test_text_chunker.py | 75 ++------ 190 files changed, 2088 insertions(+), 2173 deletions(-) create mode 100644 python/setup_dev.sh diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml index 2864db70442b..3f20ae2f0d02 100644 --- a/.github/workflows/python-lint.yml +++ b/.github/workflows/python-lint.yml @@ -7,16 +7,15 @@ on: - 'python/**' jobs: - ruff: + pre-commit: if: '!cancelled()' strategy: fail-fast: false matrix: python-version: ["3.10"] runs-on: ubuntu-latest - timeout-minutes: 5 + continue-on-error: true steps: - - run: echo "/root/.local/bin" >> $GITHUB_PATH - uses: actions/checkout@v4 - name: Install poetry run: pipx install poetry @@ -24,50 +23,6 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: "poetry" - - name: Install Semantic Kernel - run: cd python && poetry install --no-ansi - - name: Run ruff - run: cd python && poetry run ruff check . - black: - if: '!cancelled()' - strategy: - fail-fast: false - matrix: - python-version: ["3.10"] - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - run: echo "/root/.local/bin" >> $GITHUB_PATH - - uses: actions/checkout@v4 - - name: Install poetry - run: pipx install poetry - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: "poetry" - - name: Install Semantic Kernel - run: cd python && poetry install --no-ansi - - name: Run black - run: cd python && poetry run black --check . - mypy: - if: '!cancelled()' - strategy: - fail-fast: false - matrix: - python-version: ["3.10"] - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - run: echo "/root/.local/bin" >> $GITHUB_PATH - - uses: actions/checkout@v4 - - name: Install poetry - run: pipx install poetry - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: "poetry" - - name: Install Semantic Kernel - run: cd python && poetry install --no-ansi - - name: Run mypy - run: cd python && poetry run mypy -p semantic_kernel --config-file=mypy.ini - + - name: Install dependencies + run: cd python && poetry install + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/python-test-coverage.yml b/.github/workflows/python-test-coverage.yml index 7eaea6ac1f56..617dddf63c72 100644 --- a/.github/workflows/python-test-coverage.yml +++ b/.github/workflows/python-test-coverage.yml @@ -10,7 +10,6 @@ jobs: python-tests-coverage: name: Create Test Coverage Messages runs-on: ${{ matrix.os }} - continue-on-error: true permissions: pull-requests: write contents: read @@ -21,14 +20,17 @@ jobs: os: [ubuntu-latest] steps: - name: Wait for unit tests to succeed + continue-on-error: true uses: lewagon/wait-on-check-action@v1.3.4 with: ref: ${{ github.event.pull_request.head.sha }} check-name: 'Python Unit Tests (${{ matrix.python-version}}, ${{ matrix.os }})' repo-token: ${{ secrets.GH_ACTIONS_PR_WRITE }} wait-interval: 10 + allowed-conclusions: success - uses: actions/checkout@v4 - name: Download coverage + continue-on-error: true uses: dawidd6/action-download-artifact@v3 with: name: python-coverage-${{ matrix.os }}-${{ matrix.python-version }}.txt @@ -37,6 +39,7 @@ jobs: search_artifacts: true if_no_artifact_found: warn - name: Download pytest + continue-on-error: true uses: dawidd6/action-download-artifact@v3 with: name: pytest-${{ matrix.os }}-${{ matrix.python-version }}.xml @@ -45,6 +48,7 @@ jobs: search_artifacts: true if_no_artifact_found: warn - name: Pytest coverage comment + continue-on-error: true id: coverageComment uses: MishaKav/pytest-coverage-comment@main with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 34ba8f47153e..f7d2de87b67f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,23 +7,37 @@ repos: - id: sync_with_poetry args: [--config=.pre-commit-config.yaml, --db=python/.conf/packages_list.json, python/poetry.lock] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.6.0 hooks: - id: check-toml files: \.toml$ - id: check-yaml files: \.yaml$ + - id: check-json + files: \.json$ + exclude: ^python\/\.vscode\/.* - id: end-of-file-fixer files: \.py$ - id: mixed-line-ending files: \.py$ - - repo: https://github.com/psf/black - rev: 24.4.2 + - id: debug-statements + files: ^python\/semantic_kernel\/.*\.py$ + - id: check-ast + name: Check Valid Python Samples + types: ["python"] + - repo: https://github.com/nbQA-dev/nbQA + rev: 1.8.5 hooks: - - id: black - files: \.py$ + - id: nbqa-check-ast + name: Check Valid Python Notebooks + types: ["jupyter"] + - repo: https://github.com/asottile/pyupgrade + rev: v3.15.2 + hooks: + - id: pyupgrade + args: [--py310-plus] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.4 + rev: v0.4.5 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix ] @@ -36,3 +50,9 @@ repos: language: system types: [python] pass_filenames: false + - repo: https://github.com/PyCQA/bandit + rev: 1.7.8 + hooks: + - id: bandit + args: ["-c", "python/pyproject.toml"] + additional_dependencies: [ "bandit[toml]" ] \ No newline at end of file diff --git a/python/.vscode/extensions.json b/python/.vscode/extensions.json index 66114688a305..1beb54306b26 100644 --- a/python/.vscode/extensions.json +++ b/python/.vscode/extensions.json @@ -2,8 +2,9 @@ // See https://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "littlefoxteam.vscode-python-test-adapter", "streetsidesoftware.code-spell-checker", "ms-python.python", + "charliermarsh.ruff", + "rodolphebarbanneau.python-docstring-highlighter" ] } \ No newline at end of file diff --git a/python/.vscode/settings.json b/python/.vscode/settings.json index 2a36a6711298..dca92354cf5e 100644 --- a/python/.vscode/settings.json +++ b/python/.vscode/settings.json @@ -18,7 +18,6 @@ ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, - "pythonTestExplorer.testFramework": "pytest", "[python]": { "editor.codeActionsOnSave": { "source.organizeImports": "explicit", diff --git a/python/.vscode/tasks.json b/python/.vscode/tasks.json index 846585603b2d..3d7c72c4036e 100644 --- a/python/.vscode/tasks.json +++ b/python/.vscode/tasks.json @@ -93,8 +93,7 @@ "command": "poetry", "args": [ "install", - "--extras", - "all" + "--all-extras" ], "presentation": { "reveal": "silent", diff --git a/python/poetry.lock b/python/poetry.lock index 0d335cf0950a..ad85a1689abe 100644 --- a/python/poetry.lock +++ b/python/poetry.lock @@ -439,52 +439,6 @@ files = [ tests = ["pytest (>=3.2.1,!=3.3.0)"] typecheck = ["mypy"] -[[package]] -name = "black" -version = "24.4.2" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, - {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, - {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, - {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, - {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, - {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, - {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, - {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, - {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, - {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, - {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, - {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, - {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, - {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, - {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, - {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, - {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, - {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, - {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, - {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, - {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, - {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "build" version = "1.2.1" @@ -2093,13 +2047,13 @@ referencing = ">=0.31.0" [[package]] name = "jupyter-client" -version = "8.6.1" +version = "8.6.2" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_client-8.6.1-py3-none-any.whl", hash = "sha256:3b7bd22f058434e3b9a7ea4b1500ed47de2713872288c0d511d19926f99b459f"}, - {file = "jupyter_client-8.6.1.tar.gz", hash = "sha256:e842515e2bab8e19186d89fdfea7abd15e39dd581f94e399f00e2af5a1652d3f"}, + {file = "jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f"}, + {file = "jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df"}, ] [package.dependencies] @@ -2111,7 +2065,7 @@ traitlets = ">=5.3" [package.extras] docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] -test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] [[package]] name = "jupyter-core" @@ -2344,13 +2298,13 @@ files = [ [[package]] name = "microsoft-kiota-abstractions" -version = "1.3.2" +version = "1.3.3" description = "Core abstractions for kiota generated libraries in Python" optional = false python-versions = "*" files = [ - {file = "microsoft_kiota_abstractions-1.3.2-py2.py3-none-any.whl", hash = "sha256:ec4335df425874b1c0171a97c4b5ccdc4a9d076e1ecd3a5c2582af1cacc25016"}, - {file = "microsoft_kiota_abstractions-1.3.2.tar.gz", hash = "sha256:acac0b34b443d3fc10a3a86dd996cdf92248080553a3768a77c23350541f1aa2"}, + {file = "microsoft_kiota_abstractions-1.3.3-py2.py3-none-any.whl", hash = "sha256:deced0b01249459426d4ed45c8ab34e19250e514d4d05ce84c08893058ae06a1"}, + {file = "microsoft_kiota_abstractions-1.3.3.tar.gz", hash = "sha256:3cc01832a2e6dc6094c4e1abf7cbef3849a87d818a3b9193ad6c83a9f88e14ff"}, ] [package.dependencies] @@ -3188,13 +3142,13 @@ sympy = "*" [[package]] name = "openai" -version = "1.30.1" +version = "1.30.2" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.30.1-py3-none-any.whl", hash = "sha256:c9fb3c3545c118bbce8deb824397b9433a66d0d0ede6a96f7009c95b76de4a46"}, - {file = "openai-1.30.1.tar.gz", hash = "sha256:4f85190e577cba0b066e1950b8eb9b11d25bc7ebcc43a86b326ce1bfa564ec74"}, + {file = "openai-1.30.2-py3-none-any.whl", hash = "sha256:44316818fbff3845278e862a655c4c041e93d907b04eff64629c2835f29bd58e"}, + {file = "openai-1.30.2.tar.gz", hash = "sha256:f86780f40505de60fa389993d9b7f5564f20acfbe5efcabd5c853a12453af2b0"}, ] [package.dependencies] @@ -3621,17 +3575,6 @@ files = [ {file = "pathable-0.4.3.tar.gz", hash = "sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab"}, ] -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - [[package]] name = "pendulum" version = "3.0.0" @@ -5600,36 +5543,36 @@ tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc ( [[package]] name = "scipy" -version = "1.13.0" +version = "1.13.1" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "scipy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba419578ab343a4e0a77c0ef82f088238a93eef141b2b8017e46149776dfad4d"}, - {file = "scipy-1.13.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:22789b56a999265431c417d462e5b7f2b487e831ca7bef5edeb56efe4c93f86e"}, - {file = "scipy-1.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f1432ba070e90d42d7fd836462c50bf98bd08bed0aa616c359eed8a04e3922"}, - {file = "scipy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8434f6f3fa49f631fae84afee424e2483289dfc30a47755b4b4e6b07b2633a4"}, - {file = "scipy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dcbb9ea49b0167de4167c40eeee6e167caeef11effb0670b554d10b1e693a8b9"}, - {file = "scipy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:1d2f7bb14c178f8b13ebae93f67e42b0a6b0fc50eba1cd8021c9b6e08e8fb1cd"}, - {file = "scipy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fbcf8abaf5aa2dc8d6400566c1a727aed338b5fe880cde64907596a89d576fa"}, - {file = "scipy-1.13.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5e4a756355522eb60fcd61f8372ac2549073c8788f6114449b37e9e8104f15a5"}, - {file = "scipy-1.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5acd8e1dbd8dbe38d0004b1497019b2dbbc3d70691e65d69615f8a7292865d7"}, - {file = "scipy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ff7dad5d24a8045d836671e082a490848e8639cabb3dbdacb29f943a678683d"}, - {file = "scipy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4dca18c3ffee287ddd3bc8f1dabaf45f5305c5afc9f8ab9cbfab855e70b2df5c"}, - {file = "scipy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a2f471de4d01200718b2b8927f7d76b5d9bde18047ea0fa8bd15c5ba3f26a1d6"}, - {file = "scipy-1.13.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0de696f589681c2802f9090fff730c218f7c51ff49bf252b6a97ec4a5d19e8b"}, - {file = "scipy-1.13.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:b2a3ff461ec4756b7e8e42e1c681077349a038f0686132d623fa404c0bee2551"}, - {file = "scipy-1.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf9fe63e7a4bf01d3645b13ff2aa6dea023d38993f42aaac81a18b1bda7a82a"}, - {file = "scipy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e7626dfd91cdea5714f343ce1176b6c4745155d234f1033584154f60ef1ff42"}, - {file = "scipy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:109d391d720fcebf2fbe008621952b08e52907cf4c8c7efc7376822151820820"}, - {file = "scipy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:8930ae3ea371d6b91c203b1032b9600d69c568e537b7988a3073dfe4d4774f21"}, - {file = "scipy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5407708195cb38d70fd2d6bb04b1b9dd5c92297d86e9f9daae1576bd9e06f602"}, - {file = "scipy-1.13.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:ac38c4c92951ac0f729c4c48c9e13eb3675d9986cc0c83943784d7390d540c78"}, - {file = "scipy-1.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c74543c4fbeb67af6ce457f6a6a28e5d3739a87f62412e4a16e46f164f0ae5"}, - {file = "scipy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28e286bf9ac422d6beb559bc61312c348ca9b0f0dae0d7c5afde7f722d6ea13d"}, - {file = "scipy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33fde20efc380bd23a78a4d26d59fc8704e9b5fd9b08841693eb46716ba13d86"}, - {file = "scipy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:45c08bec71d3546d606989ba6e7daa6f0992918171e2a6f7fbedfa7361c2de1e"}, - {file = "scipy-1.13.0.tar.gz", hash = "sha256:58569af537ea29d3f78e5abd18398459f195546bb3be23d16677fb26616cc11e"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, + {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, + {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, + {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, + {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, + {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, + {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, + {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, + {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, + {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, ] [package.dependencies] @@ -6072,13 +6015,13 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "transformers" -version = "4.41.0" +version = "4.41.1" description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" optional = false python-versions = ">=3.8.0" files = [ - {file = "transformers-4.41.0-py3-none-any.whl", hash = "sha256:edcbc48fc7ec26b23c86a7b17a516c0c882b289df0a260f61af6d9c11bfbc3f3"}, - {file = "transformers-4.41.0.tar.gz", hash = "sha256:5971737e7c2e4d5ae1495f9d48af0351c0fb7c7c650b96508ac5996cd7f44f49"}, + {file = "transformers-4.41.1-py3-none-any.whl", hash = "sha256:f0680e0b1a01067eccd11f62f0522409422c7d6f91d532fe0f50b136a406129d"}, + {file = "transformers-4.41.1.tar.gz", hash = "sha256:fa859e4c66f0896633a3bf534e0d9a29a9a88478a49f94c5d8270537dc61cc42"}, ] [package.dependencies] @@ -6189,13 +6132,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, + {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, ] [[package]] @@ -6925,4 +6868,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = "^3.10,<3.13" -content-hash = "1a77f4eadaeaf5ec1a2d1b16a2c1f15242906e6752a95d4aeb8170f19846da4e" +content-hash = "8684feb2ffcdd5fe104c32eab1a9fa2da230e8e9d72d48e79ea0b99e9aa27b14" diff --git a/python/pyproject.toml b/python/pyproject.toml index 3d0095e384e9..303703145cdd 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -57,15 +57,14 @@ pyarrow = { version = ">=12.0.1,<16.0.0", optional = true} # Groups are for development only (installed through Poetry) [tool.poetry.group.dev.dependencies] -pre-commit = "^3.5" -black = "^24.2.0" -ruff = ">=0.3.2,<0.5.0" -ipykernel = "^6.29.3" -pytest = "^8.1.1" -pytest-asyncio = "^0.23.6" +pre-commit = ">=3.7.1" +ruff = ">=0.4.5" +ipykernel = "^6.29.4" +pytest = "^8.2.1" +pytest-asyncio = "^0.23.7" snoop = "^0.4.3" pytest-cov = ">=5.0.0" -mypy = ">=1.9.0" +mypy = ">=1.10.0" types-PyYAML = "^6.0.12.20240311" [tool.poetry.group.unit-tests] @@ -122,12 +121,29 @@ notebooks = ["ipykernel"] all = ["google-generativeai", "grpcio-status", "transformers", "sentence-transformers", "torch", "qdrant-client", "chromadb", "pymilvus", "milvus", "weaviate-client", "pinecone-client", "psycopg", "redis", "azure-search-documents", "azure-core", "azure-identity", "azure-cosmos", "usearch", "pyarrow", "ipykernel"] [tool.ruff] -lint.select = ["E", "F", "I"] line-length = 120 +target-version = "py310" +include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"] -[tool.black] -line-length = 120 +[tool.ruff.lint] +select = ["D", "E", "F", "I"] +ignore = ["D100", "D101", "D104"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.per-file-ignores] +# Ignore all directories named `tests`. +"tests/**" = ["D"] +"samples/**" = ["D"] +# Ignore all files that end in `_test.py`. +"*_test.py" = ["D"] + +[tool.bandit] +targets = ["python/semantic_kernel"] +exclude_dirs = ["python/tests"] [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + diff --git a/python/samples/concepts/auto_function_calling/azure_python_code_interpreter_function_calling.py b/python/samples/concepts/auto_function_calling/azure_python_code_interpreter_function_calling.py index baae3b2f0520..b10965a27850 100644 --- a/python/samples/concepts/auto_function_calling/azure_python_code_interpreter_function_calling.py +++ b/python/samples/concepts/auto_function_calling/azure_python_code_interpreter_function_calling.py @@ -13,9 +13,7 @@ ) from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion from semantic_kernel.contents.chat_history import ChatHistory -from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin import ( - SessionsPythonTool, -) +from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin import SessionsPythonTool from semantic_kernel.core_plugins.time_plugin import TimePlugin from semantic_kernel.exceptions.function_exceptions import FunctionExecutionException from semantic_kernel.functions.kernel_arguments import KernelArguments @@ -23,7 +21,7 @@ auth_token: AccessToken | None = None -ACA_TOKEN_ENDPOINT = "https://acasessions.io/.default" +ACA_TOKEN_ENDPOINT: str = "https://acasessions.io/.default" # nosec async def auth_callback() -> str: diff --git a/python/samples/concepts/plugins/azure_python_code_interpreter.py b/python/samples/concepts/plugins/azure_python_code_interpreter.py index ae276297bd38..2067ecbc54a7 100644 --- a/python/samples/concepts/plugins/azure_python_code_interpreter.py +++ b/python/samples/concepts/plugins/azure_python_code_interpreter.py @@ -8,15 +8,13 @@ from azure.identity import DefaultAzureCredential from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion -from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin import ( - SessionsPythonTool, -) +from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin import SessionsPythonTool from semantic_kernel.exceptions.function_exceptions import FunctionExecutionException from semantic_kernel.kernel import Kernel auth_token: AccessToken | None = None -ACA_TOKEN_ENDPOINT = "https://acasessions.io/.default" +ACA_TOKEN_ENDPOINT: str = "https://acasessions.io/.default" # nosec async def auth_callback() -> str: diff --git a/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py b/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py index 355a217294ba..b3764b960b3d 100644 --- a/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py +++ b/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py @@ -16,7 +16,7 @@ async def add_secret_to_key_vault(kernel: Kernel, plugin: KernelPlugin): """Adds a secret to the Azure Key Vault.""" arguments = KernelArguments() - arguments["secret_name"] = "Foo" + arguments["secret_name"] = "Foo" # nosec arguments["api_version"] = "7.0" arguments["value"] = "Bar" arguments["enabled"] = True @@ -31,7 +31,7 @@ async def add_secret_to_key_vault(kernel: Kernel, plugin: KernelPlugin): async def get_secret_from_key_vault(kernel: Kernel, plugin: KernelPlugin): """Gets a secret from the Azure Key Vault.""" arguments = KernelArguments() - arguments["secret_name"] = "Foo" + arguments["secret_name"] = "Foo" # nosec arguments["api_version"] = "7.0" result = await kernel.invoke( function=plugin["GetSecret"], diff --git a/python/samples/concepts/resources/email_plugin/native_function.py b/python/samples/concepts/resources/email_plugin/native_function.py index 6136babb0ac6..d48a26f48659 100644 --- a/python/samples/concepts/resources/email_plugin/native_function.py +++ b/python/samples/concepts/resources/email_plugin/native_function.py @@ -7,8 +7,7 @@ class EmailPlugin: - """ - Description: EmailPlugin provides a set of functions to send emails. + """Description: EmailPlugin provides a set of functions to send emails. Usage: kernel.add_plugin(EmailPlugin(), plugin_name="email") diff --git a/python/samples/concepts/text_generation/google_palm_text_completion.py b/python/samples/concepts/text_generation/google_palm_text_completion.py index 8971283a9f1b..d0d1cceb3501 100644 --- a/python/samples/concepts/text_generation/google_palm_text_completion.py +++ b/python/samples/concepts/text_generation/google_palm_text_completion.py @@ -7,9 +7,7 @@ async def text_completion_example_complete(kernel: Kernel, user_mssg, settings): - """ - Complete a text prompt using the Google PaLM model and print the results. - """ + """Complete a text prompt using the Google PaLM model and print the results.""" palm_text_completion = GooglePalmTextCompletion("models/text-bison-001") kernel.add_service(palm_text_completion) answer = await palm_text_completion.get_text_contents(user_mssg, settings) diff --git a/python/samples/demos/booking_restaurant/bookings_plugin/bookings_plugin.py b/python/samples/demos/booking_restaurant/bookings_plugin/bookings_plugin.py index cd7544fe55fa..36972e541e63 100644 --- a/python/samples/demos/booking_restaurant/bookings_plugin/bookings_plugin.py +++ b/python/samples/demos/booking_restaurant/bookings_plugin/bookings_plugin.py @@ -132,7 +132,6 @@ async def cancel_reservation( party_size: Annotated[int, "The number of people in the party"], ) -> Annotated[str, "The cancellation status of the reservation"]: """Cancel a reservation.""" - print(f"System > [Cancelling a reservation for {party_size} at {restaurant} on {date} at {time}]") _ = ( diff --git a/python/samples/getting_started/03-prompt-function-inline.ipynb b/python/samples/getting_started/03-prompt-function-inline.ipynb index 5acb0f8be432..134e7e72acb8 100644 --- a/python/samples/getting_started/03-prompt-function-inline.ipynb +++ b/python/samples/getting_started/03-prompt-function-inline.ipynb @@ -111,8 +111,7 @@ "outputs": [], "source": [ "from semantic_kernel.connectors.ai.open_ai import OpenAIChatPromptExecutionSettings\n", - "from semantic_kernel.prompt_template import PromptTemplateConfig, InputVariable\n", - "\n", + "from semantic_kernel.prompt_template import InputVariable, PromptTemplateConfig\n", "\n", "prompt = \"\"\"{{$input}}\n", "Summarize the content above.\n", @@ -128,7 +127,7 @@ "elif selectedService == Service.AzureOpenAI:\n", " execution_settings = OpenAIChatPromptExecutionSettings(\n", " service_id=service_id,\n", - " ai_model_id=\"gpt-35-turbo\"\n", + " ai_model_id=\"gpt-35-turbo\",\n", " max_tokens=2000,\n", " temperature=0.7,\n", " )\n", diff --git a/python/samples/getting_started/services.py b/python/samples/getting_started/services.py index 689be7ed5d5d..441f26f47e3a 100644 --- a/python/samples/getting_started/services.py +++ b/python/samples/getting_started/services.py @@ -1,16 +1,14 @@ -""" -This module defines an enumeration representing different services. +"""This module defines an enumeration representing different services. """ from enum import Enum class Service(Enum): - """ - Attributes: - OpenAI (str): Represents the OpenAI service. - AzureOpenAI (str): Represents the Azure OpenAI service. - HuggingFace (str): Represents the HuggingFace service. + """Attributes: + OpenAI (str): Represents the OpenAI service. + AzureOpenAI (str): Represents the Azure OpenAI service. + HuggingFace (str): Represents the HuggingFace service. """ OpenAI = "openai" diff --git a/python/samples/learn_resources/ai_services.py b/python/samples/learn_resources/ai_services.py index b330a62e33e1..792becd79d9e 100644 --- a/python/samples/learn_resources/ai_services.py +++ b/python/samples/learn_resources/ai_services.py @@ -18,10 +18,7 @@ async def main(): script_directory = os.path.dirname(__file__) plugins_directory = os.path.join(script_directory, "plugins") - writer_plugin = kernel.import_plugin_from_prompt_directory( - parent_directory=plugins_directory, - plugin_directory_name="WriterPlugin", - ) + writer_plugin = kernel.add_plugin(parent_directory=plugins_directory, plugin_name="WriterPlugin") # Run the ShortPoem function with the Kernel Argument. # Kernel arguments can be configured as KernelArguments object diff --git a/python/samples/learn_resources/plugins/MathPlugin/native_function.py b/python/samples/learn_resources/plugins/MathPlugin/native_function.py index de9540f420df..104ae40c649e 100644 --- a/python/samples/learn_resources/plugins/MathPlugin/native_function.py +++ b/python/samples/learn_resources/plugins/MathPlugin/native_function.py @@ -5,8 +5,7 @@ class Math: - """ - Description: MathPlugin provides a set of functions to make Math calculations. + """Description: MathPlugin provides a set of functions to make Math calculations. Usage: kernel.add_plugin(MathPlugin(), plugin_name="math") diff --git a/python/samples/learn_resources/service_configurator.py b/python/samples/learn_resources/service_configurator.py index 8423de598df4..4f735a368a89 100644 --- a/python/samples/learn_resources/service_configurator.py +++ b/python/samples/learn_resources/service_configurator.py @@ -13,8 +13,7 @@ def add_service(kernel: Kernel, use_chat: bool = True) -> Kernel: - """ - Configure the AI service for the kernel + """Configure the AI service for the kernel Args: kernel (Kernel): The kernel to configure @@ -25,7 +24,7 @@ def add_service(kernel: Kernel, use_chat: bool = True) -> Kernel: """ config = dotenv_values(".env") llm_service = config.get("GLOBAL_LLM_SERVICE", None) - assert llm_service, "The LLM_SERVICE environment variable is not set." + assert llm_service, "The LLM_SERVICE environment variable is not set." # nosec # The service_id is used to identify the service in the kernel. # This can be updated to a custom value if needed. diff --git a/python/semantic_kernel/connectors/ai/chat_completion_client_base.py b/python/semantic_kernel/connectors/ai/chat_completion_client_base.py index b2616ac841c1..d600f39cdd21 100644 --- a/python/semantic_kernel/connectors/ai/chat_completion_client_base.py +++ b/python/semantic_kernel/connectors/ai/chat_completion_client_base.py @@ -21,17 +21,16 @@ async def get_chat_message_contents( settings: "PromptExecutionSettings", **kwargs: Any, ) -> list["ChatMessageContent"]: - """ - This is the method that is called from the kernel to get a response from a chat-optimized LLM. + """This is the method that is called from the kernel to get a response from a chat-optimized LLM. - Arguments: - chat_history {ChatHistory} -- A list of chats in a chat_history object, that can be + Args: + chat_history (ChatHistory): A list of chats in a chat_history object, that can be rendered into messages from system, user, assistant and tools. - settings {PromptExecutionSettings} -- Settings for the request. - kwargs {Dict[str, Any]} -- The optional arguments. + settings (PromptExecutionSettings): Settings for the request. + kwargs (Dict[str, Any]): The optional arguments. Returns: - Union[str, List[str]] -- A string or list of strings representing the response(s) from the LLM. + Union[str, List[str]]: A string or list of strings representing the response(s) from the LLM. """ pass @@ -42,14 +41,13 @@ def get_streaming_chat_message_contents( settings: "PromptExecutionSettings", **kwargs: Any, ) -> AsyncGenerator[list["StreamingChatMessageContent"], Any]: - """ - This is the method that is called from the kernel to get a stream response from a chat-optimized LLM. + """This is the method that is called from the kernel to get a stream response from a chat-optimized LLM. - Arguments: - chat_history {ChatHistory} -- A list of chat chat_history, that can be rendered into a + Args: + chat_history (ChatHistory): A list of chat chat_history, that can be rendered into a set of chat_history, from system, user, assistant and function. - settings {PromptExecutionSettings} -- Settings for the request. - kwargs {Dict[str, Any]} -- The optional arguments. + settings (PromptExecutionSettings): Settings for the request. + kwargs (Dict[str, Any]): The optional arguments. Yields: @@ -63,18 +61,20 @@ def _prepare_chat_history_for_request( role_key: str = "role", content_key: str = "content", ) -> list[dict[str, str | None]]: - """ - Prepare the chat history for a request, allowing customization of the key names for role/author, - and optionally overriding the role. + """Prepare the chat history for a request. + + Allowing customization of the key names for role/author, and optionally overriding the role. ChatRole.TOOL messages need to be formatted different than system/user/assistant messages: They require a "tool_call_id" and (function) "name" key, and the "metadata" key should be removed. The "encoding" key should also be removed. - Arguments: - chat_history {ChatHistory} -- The chat history to prepare. + Args: + chat_history (ChatHistory): The chat history to prepare. + role_key (str): The key name for the role/author. + content_key (str): The key name for the content/message. Returns: - List[Dict[str, Optional[str]]] -- The prepared chat history. + List[Dict[str, Optional[str]]]: The prepared chat history. """ return [message.to_dict(role_key=role_key, content_key=content_key) for message in chat_history.messages] diff --git a/python/semantic_kernel/connectors/ai/embeddings/embedding_generator_base.py b/python/semantic_kernel/connectors/ai/embeddings/embedding_generator_base.py index 56abf144ab5f..571bbf53c1f9 100644 --- a/python/semantic_kernel/connectors/ai/embeddings/embedding_generator_base.py +++ b/python/semantic_kernel/connectors/ai/embeddings/embedding_generator_base.py @@ -14,4 +14,11 @@ class EmbeddingGeneratorBase(AIServiceClientBase, ABC): @abstractmethod async def generate_embeddings(self, texts: list[str], **kwargs: Any) -> "ndarray": + """Returns embeddings for the given texts as ndarray. + + Args: + texts (List[str]): The texts to generate embeddings for. + batch_size (Optional[int]): The batch size to use for the request. + kwargs (Dict[str, Any]): Additional arguments to pass to the request. + """ pass diff --git a/python/semantic_kernel/connectors/ai/google_palm/gp_prompt_execution_settings.py b/python/semantic_kernel/connectors/ai/google_palm/gp_prompt_execution_settings.py index d9943a4a0464..b3f4a618108b 100644 --- a/python/semantic_kernel/connectors/ai/google_palm/gp_prompt_execution_settings.py +++ b/python/semantic_kernel/connectors/ai/google_palm/gp_prompt_execution_settings.py @@ -40,6 +40,7 @@ class GooglePalmChatPromptExecutionSettings(GooglePalmPromptExecutionSettings): @model_validator(mode="after") def validate_input(self): + """Validate input.""" if self.prompt is not None: if self.messages or self.context or self.examples: raise ServiceInvalidExecutionSettingsError( diff --git a/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py b/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py index 0228926694cb..20ff3e853553 100644 --- a/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py @@ -38,16 +38,15 @@ def __init__( message_history: ChatHistory | None = None, env_file_path: str | None = None, ): - """ - Initializes a new instance of the GooglePalmChatCompletion class. + """Initializes a new instance of the GooglePalmChatCompletion class. - Arguments: - ai_model_id {str} -- GooglePalm model name, see + Args: + ai_model_id (str): GooglePalm model name, see https://developers.generativeai.google/models/language - api_key {str | None} -- The optional API key to use. If not provided, will be read from either + api_key (str | None): The optional API key to use. If not provided, will be read from either the env vars or the .env settings file - message_history {ChatHistory | None} -- The message history to use for context. (Optional) - env_file_path {str | None} -- Use the environment settings file as a fallback to + message_history (ChatHistory | None): The message history to use for context. (Optional) + env_file_path (str | None): Use the environment settings file as a fallback to environment variables. (Optional) """ google_palm_settings = None @@ -77,17 +76,16 @@ async def get_chat_message_contents( settings: GooglePalmPromptExecutionSettings, **kwargs: Any, ) -> list[ChatMessageContent]: - """ - This is the method that is called from the kernel to get a response from a chat-optimized LLM. + """This is the method that is called from the kernel to get a response from a chat-optimized LLM. - Arguments: - chat_history {List[ChatMessage]} -- A list of chat messages, that can be rendered into a + Args: + chat_history (List[ChatMessage]): A list of chat messages, that can be rendered into a set of messages, from system, user, assistant and function. - settings {GooglePalmPromptExecutionSettings} -- Settings for the request. - kwargs {Dict[str, Any]} -- The optional arguments. + settings (GooglePalmPromptExecutionSettings): Settings for the request. + kwargs (Dict[str, Any]): The optional arguments. Returns: - List[ChatMessageContent] -- A list of ChatMessageContent objects representing the response(s) from the LLM. + List[ChatMessageContent]: A list of ChatMessageContent objects representing the response(s) from the LLM. """ settings.messages = self._prepare_chat_history_for_request(chat_history, role_key="author") if not settings.ai_model_id: @@ -103,11 +101,13 @@ def _create_chat_message_content( ) -> ChatMessageContent: """Create a chat message content object from a response. - Arguments: - response {ChatResponse} -- The response to create the content from. + Args: + response (ChatResponse): The response to create the content from. + candidate (MessageDict): The candidate message to create the content from. + index (int): The index of the candidate message. Returns: - ChatMessageContent -- The created chat message content. + ChatMessageContent: The created chat message content. """ metadata = { "citation_metadata": candidate.get("citation_metadata"), @@ -128,6 +128,11 @@ async def get_streaming_chat_message_contents( settings: GooglePalmPromptExecutionSettings, **kwargs: Any, ): + """Return a streaming chat message. + + Raises: + NotImplementedError: Google Palm API does not currently support streaming + """ raise NotImplementedError("Google Palm API does not currently support streaming") async def get_text_contents( @@ -135,15 +140,14 @@ async def get_text_contents( prompt: str, settings: GooglePalmPromptExecutionSettings, ) -> list[TextContent]: - """ - This is the method that is called from the kernel to get a response from a text-optimized LLM. + """This is the method that is called from the kernel to get a response from a text-optimized LLM. - Arguments: - prompt {str} -- The prompt to send to the LLM. - settings {GooglePalmPromptExecutionSettings} -- Settings for the request. + Args: + prompt (str): The prompt to send to the LLM. + settings (GooglePalmPromptExecutionSettings): Settings for the request. Returns: - List[TextContent] -- A list of TextContent objects representing the response(s) from the LLM. + List[TextContent]: A list of TextContent objects representing the response(s) from the LLM. """ settings.messages = [{"author": "user", "content": prompt}] if not settings.ai_model_id: @@ -155,11 +159,12 @@ async def get_text_contents( def _create_text_content(self, response: ChatResponse, candidate: MessageDict) -> TextContent: """Create a text content object from a response. - Arguments: - response {ChatResponse} -- The response to create the content from. + Args: + response (ChatResponse): The response to create the content from. + candidate (MessageDict): The candidate message to create the content from. Returns: - TextContent -- The created text content. + TextContent: The created text content. """ metadata = {"citation_metadata": candidate.get("citation_metadata"), "filters": response.filters} return TextContent( @@ -174,41 +179,31 @@ async def get_streaming_text_contents( prompt: str, settings: GooglePalmPromptExecutionSettings, ): + """Return a streaming text content. + + Raises: + NotImplementedError: Google Palm API does not currently support streaming + """ raise NotImplementedError("Google Palm API does not currently support streaming") async def _send_chat_request( self, settings: GooglePalmPromptExecutionSettings, - ): - """ - Completes the given user message. If len(messages) > 1, and a + ) -> Any: + """Completes the given user message. + + If len(messages) > 1, and a conversation has not been initiated yet, it is assumed that chat history is needed for context. All messages preceding the last message will be utilized for context. This also enables Google PaLM to utilize memory and plugins, which should be stored in the messages parameter as system messages. - Arguments: - messages {str} -- The message (from a user) to respond to. - settings {GooglePalmPromptExecutionSettings} -- The request settings. - context {str} -- Text that should be provided to the model first, - to ground the response. If a system message is provided, it will be - used as context. - examples {ExamplesOptions} -- Examples of what the model should - generate. This includes both the user input and the response that - the model should emulate. These examples are treated identically to - conversation messages except that they take precedence over the - history in messages: If the total input size exceeds the model's - input_token_limit the input will be truncated. Items will be dropped - from messages before examples - See: https://developers.generativeai.google/api/python/google/generativeai/types/ExampleOptions - prompt {MessagePromptOptions} -- You may pass a - types.MessagePromptOptions instead of a setting context/examples/messages, - but not both. - See: https://developers.generativeai.google/api/python/google/generativeai/types/MessagePromptOptions + Args: + settings (GooglePalmPromptExecutionSettings): The request settings. Returns: - str -- The completed text. + The completion. """ if settings is None: raise ValueError("The request settings cannot be `None`") diff --git a/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py b/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py index 8a9ca161acdc..ecb4117d0f67 100644 --- a/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py +++ b/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py @@ -22,15 +22,14 @@ class GooglePalmTextCompletion(TextCompletionClientBase): api_key: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] def __init__(self, ai_model_id: str, api_key: str | None = None, env_file_path: str | None = None): - """ - Initializes a new instance of the GooglePalmTextCompletion class. + """Initializes a new instance of the GooglePalmTextCompletion class. - Arguments: - ai_model_id {str} -- GooglePalm model name, see + Args: + ai_model_id (str): GooglePalm model name, see https://developers.generativeai.google/models/language - api_key {str | None} -- The optional API key to use. If not provided, will be + api_key (str | None): The optional API key to use. If not provided, will be read from either the env vars or the .env settings file. - env_file_path {str | None} -- Use the environment settings file as a + env_file_path (str | None): Use the environment settings file as a fallback to environment variables. (Optional) """ try: @@ -52,15 +51,14 @@ def __init__(self, ai_model_id: str, api_key: str | None = None, env_file_path: async def get_text_contents( self, prompt: str, settings: GooglePalmTextPromptExecutionSettings ) -> list[TextContent]: - """ - This is the method that is called from the kernel to get a response from a text-optimized LLM. + """This is the method that is called from the kernel to get a response from a text-optimized LLM. - Arguments: - prompt {str} -- The prompt to send to the LLM. - settings {GooglePalmTextPromptExecutionSettings} -- Settings for the request. + Args: + prompt (str): The prompt to send to the LLM. + settings (GooglePalmTextPromptExecutionSettings): Settings for the request. Returns: - List[TextContent] -- A list of TextContent objects representing the response(s) from the LLM. + List[TextContent]: A list of TextContent objects representing the response(s) from the LLM. """ settings.prompt = prompt if not settings.ai_model_id: @@ -100,6 +98,12 @@ async def get_streaming_text_contents( prompt: str, settings: GooglePalmTextPromptExecutionSettings, ): + """Get streaming text contents from the Google Palm API, unsupported. + + Raises: + NotImplementedError: Google Palm API does not currently support streaming. + + """ raise NotImplementedError("Google Palm API does not currently support streaming") def get_prompt_execution_settings_class(self) -> "PromptExecutionSettings": diff --git a/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_embedding.py b/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_embedding.py index 6631d8633477..5678a79ae514 100644 --- a/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_embedding.py @@ -20,15 +20,14 @@ class GooglePalmTextEmbedding(EmbeddingGeneratorBase): api_key: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] def __init__(self, ai_model_id: str, api_key: str | None = None, env_file_path: str | None = None) -> None: - """ - Initializes a new instance of the GooglePalmTextEmbedding class. + """Initializes a new instance of the GooglePalmTextEmbedding class. - Arguments: - ai_model_id {str} -- GooglePalm model name, see + Args: + ai_model_id (str): GooglePalm model name, see https://developers.generativeai.google/models/language - api_key {str | None} -- The optional API key to use. If not provided, will be + api_key (str | None): The optional API key to use. If not provided, will be read from either the env vars or the .env settings file. - env_file_path {str | None} -- Use the environment settings file + env_file_path (str | None): Use the environment settings file as a fallback to environment variables. (Optional) """ try: @@ -49,15 +48,7 @@ def __init__(self, ai_model_id: str, api_key: str | None = None, env_file_path: super().__init__(ai_model_id=ai_model_id, api_key=api_key) async def generate_embeddings(self, texts: list[str], **kwargs: Any) -> ndarray: - """ - Generates embeddings for a list of texts. - - Arguments: - texts {List[str]} -- Texts to generate embeddings for. - - Returns: - ndarray -- Embeddings for the texts. - """ + """Generates embeddings for the given list of texts.""" try: palm.configure(api_key=self.api_key) except Exception as ex: diff --git a/python/semantic_kernel/connectors/ai/google_palm/settings/google_palm_settings.py b/python/semantic_kernel/connectors/ai/google_palm/settings/google_palm_settings.py index db0cdb2d6466..586d83d48823 100644 --- a/python/semantic_kernel/connectors/ai/google_palm/settings/google_palm_settings.py +++ b/python/semantic_kernel/connectors/ai/google_palm/settings/google_palm_settings.py @@ -5,7 +5,7 @@ class GooglePalmSettings(BaseSettings): - """Google Palm model settings + """Google Palm model settings. The settings are first loaded from environment variables with the prefix 'GOOGLE_PALM_'. If the environment variables are not found, the settings can be loaded from a .env file with the @@ -31,6 +31,8 @@ class GooglePalmSettings(BaseSettings): embedding_model_id: str | None = None class Config: + """Pydantic configuration settings.""" + env_prefix = "GOOGLE_PALM_" env_file = None env_file_encoding = "utf-8" @@ -39,6 +41,7 @@ class Config: @classmethod def create(cls, **kwargs): + """Create the settings object.""" if "env_file_path" in kwargs and kwargs["env_file_path"]: cls.Config.env_file = kwargs["env_file_path"] else: diff --git a/python/semantic_kernel/connectors/ai/hugging_face/hf_prompt_execution_settings.py b/python/semantic_kernel/connectors/ai/hugging_face/hf_prompt_execution_settings.py index 548671f02309..fcfd92ee23c3 100644 --- a/python/semantic_kernel/connectors/ai/hugging_face/hf_prompt_execution_settings.py +++ b/python/semantic_kernel/connectors/ai/hugging_face/hf_prompt_execution_settings.py @@ -16,6 +16,7 @@ class HuggingFacePromptExecutionSettings(PromptExecutionSettings): top_p: float = 1.0 def get_generation_config(self) -> GenerationConfig: + """Get the generation config.""" return GenerationConfig( **self.model_dump( include={"max_new_tokens", "pad_token_id", "eos_token_id", "temperature", "top_p"}, @@ -26,6 +27,7 @@ def get_generation_config(self) -> GenerationConfig: ) def prepare_settings_dict(self, **kwargs) -> dict[str, Any]: + """Prepare the settings dictionary.""" gen_config = self.get_generation_config() settings = { "generation_config": gen_config, diff --git a/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py b/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py index 69153e86328e..05465ef607a6 100644 --- a/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py +++ b/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py @@ -34,26 +34,25 @@ def __init__( model_kwargs: dict[str, Any] | None = None, pipeline_kwargs: dict[str, Any] | None = None, ) -> None: - """ - Initializes a new instance of the HuggingFaceTextCompletion class. + """Initializes a new instance of the HuggingFaceTextCompletion class. - Arguments: - ai_model_id {str} -- Hugging Face model card string, see + Args: + ai_model_id (str): Hugging Face model card string, see https://huggingface.co/models - device {Optional[int]} -- Device to run the model on, defaults to CPU, 0+ for GPU, + device (Optional[int]): Device to run the model on, defaults to CPU, 0+ for GPU, -- None if using device_map instead. (If both device and device_map are specified, device overrides device_map. If unintended, it can lead to unexpected behavior.) - service_id {Optional[str]} -- Service ID for the AI service. - task {Optional[str]} -- Model completion task type, options are: + service_id (Optional[str]): Service ID for the AI service. + task (Optional[str]): Model completion task type, options are: - summarization: takes a long text and returns a shorter summary. - text-generation: takes incomplete text and returns a set of completion candidates. - text2text-generation (default): takes an input prompt and returns a completion. text2text-generation is the default as it behaves more like GPT-3+. - log -- Logger instance. (Deprecated) - model_kwargs {Optional[Dict[str, Any]]} -- Additional dictionary of keyword arguments + log : Logger instance. (Deprecated) + model_kwargs (Optional[Dict[str, Any]]): Additional dictionary of keyword arguments passed along to the model's `from_pretrained(..., **model_kwargs)` function. - pipeline_kwargs {Optional[Dict[str, Any]]} -- Additional keyword arguments passed along + pipeline_kwargs (Optional[Dict[str, Any]]): Additional keyword arguments passed along to the specific pipeline init (see the documentation for the corresponding pipeline class for possible values). @@ -79,15 +78,14 @@ async def get_text_contents( prompt: str, settings: HuggingFacePromptExecutionSettings, ) -> list[TextContent]: - """ - This is the method that is called from the kernel to get a response from a text-optimized LLM. + """This is the method that is called from the kernel to get a response from a text-optimized LLM. - Arguments: - prompt {str} -- The prompt to send to the LLM. - settings {HuggingFacePromptExecutionSettings} -- Settings for the request. + Args: + prompt (str): The prompt to send to the LLM. + settings (HuggingFacePromptExecutionSettings): Settings for the request. Returns: - List[TextContent] -- A list of TextContent objects representing the response(s) from the LLM. + List[TextContent]: A list of TextContent objects representing the response(s) from the LLM. """ try: results = self.generator(prompt, **settings.prepare_settings_dict()) @@ -109,16 +107,16 @@ async def get_streaming_text_contents( prompt: str, settings: HuggingFacePromptExecutionSettings, ) -> AsyncGenerator[list[StreamingTextContent], Any]: - """ - Streams a text completion using a Hugging Face model. + """Streams a text completion using a Hugging Face model. + Note that this method does not support multiple responses. - Arguments: - prompt {str} -- Prompt to complete. - settings {HuggingFacePromptExecutionSettings} -- Request settings. + Args: + prompt (str): Prompt to complete. + settings (HuggingFacePromptExecutionSettings): Request settings. Yields: - List[StreamingTextContent] -- List of StreamingTextContent objects. + List[StreamingTextContent]: List of StreamingTextContent objects. """ if settings.num_return_sequences > 1: raise ServiceInvalidExecutionSettingsError( diff --git a/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_embedding.py b/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_embedding.py index 4c205283346d..fd54c14d7e4f 100644 --- a/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_embedding.py @@ -1,8 +1,14 @@ # Copyright (c) Microsoft. All rights reserved. import logging +import sys from typing import Any +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + import sentence_transformers import torch from numpy import array, ndarray @@ -25,14 +31,13 @@ def __init__( device: int | None = -1, service_id: str | None = None, ) -> None: - """ - Initializes a new instance of the HuggingFaceTextEmbedding class. + """Initializes a new instance of the HuggingFaceTextEmbedding class. - Arguments: - ai_model_id {str} -- Hugging Face model card string, see + Args: + ai_model_id (str): Hugging Face model card string, see https://huggingface.co/sentence-transformers - device {Optional[int]} -- Device to run the model on, -1 for CPU, 0+ for GPU. - log -- The logger instance to use. (Optional) (Deprecated) + device (Optional[int]): Device to run the model on, -1 for CPU, 0+ for GPU. + service_id (Optional[str]): Service ID for the model. Note that this model will be downloaded from the Hugging Face model hub. """ @@ -44,16 +49,8 @@ def __init__( generator=sentence_transformers.SentenceTransformer(model_name_or_path=ai_model_id, device=resolved_device), ) + @override async def generate_embeddings(self, texts: list[str], **kwargs: Any) -> ndarray: - """ - Generates embeddings for a list of texts. - - Arguments: - texts {List[str]} -- Texts to generate embeddings for. - - Returns: - ndarray -- Embeddings for the texts. - """ try: logger.info(f"Generating embeddings for {len(texts)} texts") embeddings = self.generator.encode(texts, **kwargs) diff --git a/python/semantic_kernel/connectors/ai/ollama/services/ollama_chat_completion.py b/python/semantic_kernel/connectors/ai/ollama/services/ollama_chat_completion.py index 43b301cee199..3ffe48ba0b7e 100644 --- a/python/semantic_kernel/connectors/ai/ollama/services/ollama_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/ollama/services/ollama_chat_completion.py @@ -22,15 +22,14 @@ class OllamaChatCompletion(TextCompletionClientBase, ChatCompletionClientBase): - """ - Initializes a new instance of the OllamaChatCompletion class. + """Initializes a new instance of the OllamaChatCompletion class. Make sure to have the ollama service running either locally or remotely. - Arguments: - ai_model_id {str} -- Ollama model name, see https://ollama.ai/library - url {Optional[Union[str, HttpUrl]]} -- URL of the Ollama server, defaults to http://localhost:11434/api/chat - session {Optional[aiohttp.ClientSession]} -- Optional client session to use for requests. + Args: + ai_model_id (str): Ollama model name, see https://ollama.ai/library + url (Optional[Union[str, HttpUrl]]): URL of the Ollama server, defaults to http://localhost:11434/api/chat + session (Optional[aiohttp.ClientSession]): Optional client session to use for requests. """ url: HttpUrl = "http://localhost:11434/api/chat" @@ -42,17 +41,16 @@ async def get_chat_message_contents( settings: OllamaChatPromptExecutionSettings, **kwargs: Any, ) -> list[ChatMessageContent]: - """ - This is the method that is called from the kernel to get a response from a chat-optimized LLM. + """This is the method that is called from the kernel to get a response from a chat-optimized LLM. - Arguments: - chat_history {ChatHistory} -- A chat history that contains a list of chat messages, + Args: + chat_history (ChatHistory): A chat history that contains a list of chat messages, that can be rendered into a set of messages, from system, user, assistant and function. - settings {PromptExecutionSettings} -- Settings for the request. - kwargs {Dict[str, Any]} -- The optional arguments. + settings (PromptExecutionSettings): Settings for the request. + kwargs (Dict[str, Any]): The optional arguments. Returns: - List[ChatMessageContent] -- A list of ChatMessageContent objects representing the response(s) from the LLM. + List[ChatMessageContent]: A list of ChatMessageContent objects representing the response(s) from the LLM. """ if not settings.ai_model_id: settings.ai_model_id = self.ai_model_id @@ -77,18 +75,18 @@ async def get_streaming_chat_message_contents( settings: OllamaChatPromptExecutionSettings, **kwargs: Any, ) -> AsyncGenerator[list[StreamingChatMessageContent], Any]: - """ - Streams a text completion using an Ollama model. + """Streams a text completion using an Ollama model. + Note that this method does not support multiple responses. - Arguments: - chat_history {ChatHistory} -- A chat history that contains a list of chat messages, + Args: + chat_history (ChatHistory): A chat history that contains a list of chat messages, that can be rendered into a set of messages, from system, user, assistant and function. - settings {OllamaChatPromptExecutionSettings} -- Request settings. - kwargs {Dict[str, Any]} -- The optional arguments. + settings (OllamaChatPromptExecutionSettings): Request settings. + kwargs (Dict[str, Any]): The optional arguments. Yields: - List[StreamingChatMessageContent] -- Stream of StreamingChatMessageContent objects. + List[StreamingChatMessageContent]: Stream of StreamingChatMessageContent objects. """ if not settings.ai_model_id: settings.ai_model_id = self.ai_model_id @@ -118,15 +116,14 @@ async def get_text_contents( prompt: str, settings: OllamaChatPromptExecutionSettings, ) -> list[TextContent]: - """ - This is the method that is called from the kernel to get a response from a text-optimized LLM. + """This is the method that is called from the kernel to get a response from a text-optimized LLM. - Arguments: - chat_history {ChatHistory} -- A chat history that contains the prompt to complete. - settings {OllamaChatPromptExecutionSettings} -- Settings for the request. + Args: + prompt (str): A prompt to complete + settings (OllamaChatPromptExecutionSettings): Settings for the request. Returns: - List["TextContent"] -- The completion result(s). + List["TextContent"]: The completion result(s). """ if not settings.ai_model_id: settings.ai_model_id = self.ai_model_id @@ -149,18 +146,17 @@ async def get_streaming_text_contents( prompt: str, settings: OllamaChatPromptExecutionSettings, ) -> AsyncGenerator[list[StreamingTextContent], Any]: - """ - Streams a text completion using an Ollama model. + """Streams a text completion using an Ollama model. + Note that this method does not support multiple responses. - Arguments: - prompt {str} -- A chat history that contains the prompt to complete. - settings {OllamaChatPromptExecutionSettings} -- Request settings. + Args: + prompt (str): A chat history that contains the prompt to complete. + settings (OllamaChatPromptExecutionSettings): Request settings. Yields: - List["StreamingTextContent"] -- The result stream made up of StreamingTextContent objects. + List["StreamingTextContent"]: The result stream made up of StreamingTextContent objects. """ - if not settings.ai_model_id: settings.ai_model_id = self.ai_model_id settings.messages = [{"role": "user", "content": prompt}] diff --git a/python/semantic_kernel/connectors/ai/ollama/services/ollama_text_completion.py b/python/semantic_kernel/connectors/ai/ollama/services/ollama_text_completion.py index 690a7cf6fde0..60d25bf4045c 100644 --- a/python/semantic_kernel/connectors/ai/ollama/services/ollama_text_completion.py +++ b/python/semantic_kernel/connectors/ai/ollama/services/ollama_text_completion.py @@ -18,14 +18,13 @@ class OllamaTextCompletion(TextCompletionClientBase): - """ - Initializes a new instance of the OllamaTextCompletion class. + """Initializes a new instance of the OllamaTextCompletion class. Make sure to have the ollama service running either locally or remotely. - Arguments: - ai_model_id {str} -- Ollama model name, see https://ollama.ai/library - url {Optional[Union[str, HttpUrl]]} -- URL of the Ollama server, defaults to http://localhost:11434/api/generate + Args: + ai_model_id (str): Ollama model name, see https://ollama.ai/library + url (Optional[Union[str, HttpUrl]]): URL of the Ollama server, defaults to http://localhost:11434/api/generate """ url: HttpUrl = "http://localhost:11434/api/generate" @@ -36,15 +35,14 @@ async def get_text_contents( prompt: str, settings: OllamaTextPromptExecutionSettings, ) -> list[TextContent]: - """ - This is the method that is called from the kernel to get a response from a text-optimized LLM. + """This is the method that is called from the kernel to get a response from a text-optimized LLM. - Arguments: - prompt {str} -- The prompt to send to the LLM. - settings {OllamaTextPromptExecutionSettings} -- Settings for the request. + Args: + prompt (str): The prompt to send to the LLM. + settings (OllamaTextPromptExecutionSettings): Settings for the request. Returns: - List[TextContent] -- A list of TextContent objects representing the response(s) from the LLM. + List[TextContent]: A list of TextContent objects representing the response(s) from the LLM. """ if not settings.ai_model_id: settings.ai_model_id = self.ai_model_id @@ -62,17 +60,17 @@ async def get_streaming_text_contents( prompt: str, settings: OllamaTextPromptExecutionSettings, ) -> AsyncGenerator[list[StreamingTextContent], Any]: - """ - Streams a text completion using an Ollama model. + """Streams a text completion using an Ollama model. + Note that this method does not support multiple responses, but the result will be a list anyway. - Arguments: - prompt {str} -- Prompt to complete. - settings {OllamaTextPromptExecutionSettings} -- Request settings. + Args: + prompt (str): Prompt to complete. + settings (OllamaTextPromptExecutionSettings): Request settings. Yields: - List[StreamingTextContent] -- Completion result. + List[StreamingTextContent]: Completion result. """ if not settings.ai_model_id: settings.ai_model_id = self.ai_model_id diff --git a/python/semantic_kernel/connectors/ai/ollama/services/ollama_text_embedding.py b/python/semantic_kernel/connectors/ai/ollama/services/ollama_text_embedding.py index 4616e27c5e8f..0be5c3b8e7ae 100644 --- a/python/semantic_kernel/connectors/ai/ollama/services/ollama_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/ollama/services/ollama_text_embedding.py @@ -1,8 +1,14 @@ # Copyright (c) Microsoft. All rights reserved. import logging +import sys from typing import Any +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + import aiohttp from numpy import array, ndarray from pydantic import HttpUrl @@ -20,25 +26,17 @@ class OllamaTextEmbedding(EmbeddingGeneratorBase): Make sure to have the ollama service running either locally or remotely. - Arguments: - ai_model_id {str} -- Ollama model name, see https://ollama.ai/library - url {Optional[Union[str, HttpUrl]]} -- URL of the Ollama server, defaults to http://localhost:11434/api/embeddings - session {Optional[aiohttp.ClientSession]} -- Optional client session to use for requests. + Args: + ai_model_id (str): Ollama model name, see https://ollama.ai/library + url (Optional[Union[str, HttpUrl]]): URL of the Ollama server, defaults to http://localhost:11434/api/embeddings + session (Optional[aiohttp.ClientSession]): Optional client session to use for requests. """ url: HttpUrl = "http://localhost:11434/api/embeddings" session: aiohttp.ClientSession | None = None + @override async def generate_embeddings(self, texts: list[str], **kwargs: Any) -> ndarray: - """ - Generates embeddings for a list of texts. - - Arguments: - texts {List[str]} -- Texts to generate embeddings for. - - Returns: - ndarray -- Embeddings for the texts. - """ result = [] for text in texts: async with AsyncSession(self.session) as session: diff --git a/python/semantic_kernel/connectors/ai/ollama/utils.py b/python/semantic_kernel/connectors/ai/ollama/utils.py index 60f83e8134ce..4b21cf5199c5 100644 --- a/python/semantic_kernel/connectors/ai/ollama/utils.py +++ b/python/semantic_kernel/connectors/ai/ollama/utils.py @@ -5,10 +5,13 @@ class AsyncSession: def __init__(self, session: aiohttp.ClientSession = None): + """Initialize the AsyncSession.""" self._session = session if session else aiohttp.ClientSession() async def __aenter__(self): + """Enter the context manager.""" return await self._session.__aenter__() async def __aexit__(self, *args, **kwargs): + """Exit the context manager.""" await self._session.close() diff --git a/python/semantic_kernel/connectors/ai/open_ai/exceptions/content_filter_ai_exception.py b/python/semantic_kernel/connectors/ai/open_ai/exceptions/content_filter_ai_exception.py index 182aa42b4981..d9ef8b4c65d2 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/exceptions/content_filter_ai_exception.py +++ b/python/semantic_kernel/connectors/ai/open_ai/exceptions/content_filter_ai_exception.py @@ -25,12 +25,12 @@ class ContentFilterResult: def from_inner_error_result(cls, inner_error_results: dict[str, Any]) -> "ContentFilterResult": """Creates a ContentFilterResult from the inner error results. - Arguments: - key {str} -- The key to get the inner error result from. - inner_error_results {Dict[str, Any]} -- The inner error results. + Args: + key (str): The key to get the inner error result from. + inner_error_results (Dict[str, Any]): The inner error results. Returns: - ContentFilterResult -- The ContentFilterResult. + ContentFilterResult: The ContentFilterResult. """ return cls( filtered=inner_error_results.get("filtered", False), @@ -47,7 +47,7 @@ class ContentFilterCodes(Enum): @dataclass class ContentFilterAIException(ServiceContentFilterException): - """AI exception for an error from Azure OpenAI's content filter""" + """AI exception for an error from Azure OpenAI's content filter.""" # The parameter that caused the error. param: str @@ -65,9 +65,9 @@ def __init__( ) -> None: """Initializes a new instance of the ContentFilterAIException class. - Arguments: - message {str} -- The error message. - inner_exception {Exception} -- The inner exception. + Args: + message (str): The error message. + inner_exception (Exception): The inner exception. """ super().__init__(message) diff --git a/python/semantic_kernel/connectors/ai/open_ai/prompt_execution_settings/azure_chat_prompt_execution_settings.py b/python/semantic_kernel/connectors/ai/open_ai/prompt_execution_settings/azure_chat_prompt_execution_settings.py index d5c28f6f0b05..3a2398457c5f 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/prompt_execution_settings/azure_chat_prompt_execution_settings.py +++ b/python/semantic_kernel/connectors/ai/open_ai/prompt_execution_settings/azure_chat_prompt_execution_settings.py @@ -92,6 +92,7 @@ class ExtraBody(KernelBaseModel): output_language: str | None = Field(None, serialization_alias="outputLanguage") def __getitem__(self, item): + """Get an item from the ExtraBody.""" return getattr(self, item) diff --git a/python/semantic_kernel/connectors/ai/open_ai/prompt_execution_settings/open_ai_prompt_execution_settings.py b/python/semantic_kernel/connectors/ai/open_ai/prompt_execution_settings/open_ai_prompt_execution_settings.py index 1341961dba0f..87d0a5ddbfb9 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/prompt_execution_settings/open_ai_prompt_execution_settings.py +++ b/python/semantic_kernel/connectors/ai/open_ai/prompt_execution_settings/open_ai_prompt_execution_settings.py @@ -41,7 +41,6 @@ class OpenAITextPromptExecutionSettings(OpenAIPromptExecutionSettings): @model_validator(mode="after") def check_best_of_and_n(self) -> "OpenAITextPromptExecutionSettings": """Check that the best_of parameter is not greater than the number_of_responses parameter.""" - best_of = self.best_of or self.extension_data.get("best_of") number_of_responses = self.number_of_responses or self.extension_data.get("number_of_responses") @@ -67,6 +66,7 @@ class OpenAIChatPromptExecutionSettings(OpenAIPromptExecutionSettings): @field_validator("functions", "function_call", mode="after") @classmethod def validate_function_call(cls, v: str | list[dict[str, Any]] | None = None): + """Validate the function_call and functions parameters.""" if v is not None: logger.warning( "The function_call and functions parameters are deprecated. Please use the tool_choice and tools parameters instead." # noqa: E501 diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py index 728070f1283e..586a911027ad 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py @@ -52,27 +52,26 @@ def __init__( async_client: AsyncAzureOpenAI | None = None, env_file_path: str | None = None, ) -> None: - """ - Initialize an AzureChatCompletion service. + """Initialize an AzureChatCompletion service. - Arguments: - service_id {str | None}: The service ID for the Azure deployment. (Optional) - api_key {str | None}: The optional api key. If provided, will override the value in the + Args: + service_id (str | None): The service ID for the Azure deployment. (Optional) + api_key (str | None): The optional api key. If provided, will override the value in the env vars or .env file. - deployment_name {str | None}: The optional deployment. If provided, will override the value + deployment_name (str | None): The optional deployment. If provided, will override the value (chat_deployment_name) in the env vars or .env file. - endpoint {str | None}: The optional deployment endpoint. If provided will override the value + endpoint (str | None): The optional deployment endpoint. If provided will override the value in the env vars or .env file. - base_url {str | None}: The optional deployment base_url. If provided will override the value + base_url (str | None): The optional deployment base_url. If provided will override the value in the env vars or .env file. - api_version {str | None}: The optional deployment api version. If provided will override the value + api_version (str | None): The optional deployment api version. If provided will override the value in the env vars or .env file. - ad_token {str | None}: The Azure Active Directory token. (Optional) - ad_token_provider {AsyncAzureADTokenProvider}: The Azure Active Directory token provider. (Optional) - default_headers {Mapping[str, str]}: The default headers mapping of string keys to + ad_token (str | None): The Azure Active Directory token. (Optional) + ad_token_provider (AsyncAzureADTokenProvider): The Azure Active Directory token provider. (Optional) + default_headers (Mapping[str, str]): The default headers mapping of string keys to string values for HTTP requests. (Optional) - async_client {AsyncAzureOpenAI | None} -- An existing client to use. (Optional) - env_file_path {str | None} -- Use the environment settings file as a fallback to using env vars. + async_client (AsyncAzureOpenAI | None): An existing client to use. (Optional) + env_file_path (str | None): Use the environment settings file as a fallback to using env vars. """ azure_openai_settings = None try: @@ -122,15 +121,13 @@ def __init__( @classmethod def from_dict(cls, settings: dict[str, str]) -> "AzureChatCompletion": - """ - Initialize an Azure OpenAI service from a dictionary of settings. + """Initialize an Azure OpenAI service from a dictionary of settings. - Arguments: + Args: settings: A dictionary of settings for the service. should contain keys: service_id, and optionally: ad_auth, ad_token_provider, default_headers """ - return AzureChatCompletion( service_id=settings.get("service_id"), api_key=settings.get("api_key", None), diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py index e2ba6ef14bfb..48347fa3efd8 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_config_base.py @@ -35,21 +35,23 @@ def __init__( ) -> None: """Internal class for configuring a connection to an Azure OpenAI service. - Arguments: - deployment_name {str} -- Name of the deployment. - ai_model_type {OpenAIModelTypes} -- The type of OpenAI model to deploy. - endpoint {Optional[HttpsUrl]} -- The specific endpoint URL for the deployment. (Optional) - base_url {Optional[HttpsUrl]} -- The base URL for Azure services. (Optional) - api_version {str} -- Azure API version. Defaults to the defined DEFAULT_AZURE_API_VERSION. - api_key {Optional[str]} -- API key for Azure services. (Optional) - ad_token {Optional[str]} -- Azure AD token for authentication. (Optional) - ad_token_provider {Optional[Callable[[], Union[str, Awaitable[str]]]]} -- A callable - or coroutine function providing Azure AD tokens. (Optional) - default_headers {Union[Mapping[str, str], None]} -- Default headers for HTTP requests. (Optional) - async_client {Optional[AsyncAzureOpenAI]} -- An existing client to use. (Optional) - The `validate_call` decorator is used with a configuration that allows arbitrary types. This is necessary for types like `HttpsUrl` and `OpenAIModelTypes`. + + Args: + deployment_name (str): Name of the deployment. + ai_model_type (OpenAIModelTypes): The type of OpenAI model to deploy. + endpoint (Optional[HttpsUrl]): The specific endpoint URL for the deployment. (Optional) + base_url (Optional[HttpsUrl]): The base URL for Azure services. (Optional) + api_version (str): Azure API version. Defaults to the defined DEFAULT_AZURE_API_VERSION. + service_id (Optional[str]): Service ID for the deployment. (Optional) + api_key (Optional[str]): API key for Azure services. (Optional) + ad_token (Optional[str]): Azure AD token for authentication. (Optional) + ad_token_provider (Optional[Callable[[], Union[str, Awaitable[str]]]]): A callable + or coroutine function providing Azure AD tokens. (Optional) + default_headers (Union[Mapping[str, str], None]): Default headers for HTTP requests. (Optional) + async_client (Optional[AsyncAzureOpenAI]): An existing client to use. (Optional) + """ # Merge APP_INFO into the headers if it exists merged_headers = default_headers.copy() if default_headers else {} @@ -91,6 +93,7 @@ def __init__( super().__init__(**args) def to_dict(self) -> dict[str, str]: + """Convert the configuration to a dictionary.""" client_settings = { "base_url": str(self.client.base_url), "api_version": self.client._custom_query["api-version"], diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py index 693317d99be0..15b3c01835db 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py @@ -8,15 +8,9 @@ from pydantic import ValidationError from semantic_kernel.connectors.ai.open_ai.const import DEFAULT_AZURE_API_VERSION -from semantic_kernel.connectors.ai.open_ai.services.azure_config_base import ( - AzureOpenAIConfigBase, -) -from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import ( - OpenAIModelTypes, -) -from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import ( - OpenAITextCompletionBase, -) +from semantic_kernel.connectors.ai.open_ai.services.azure_config_base import AzureOpenAIConfigBase +from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import OpenAIModelTypes +from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import OpenAITextCompletionBase from semantic_kernel.connectors.ai.open_ai.settings.azure_open_ai_settings import AzureOpenAISettings from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError from semantic_kernel.kernel_pydantic import HttpsUrl @@ -41,27 +35,26 @@ def __init__( async_client: AsyncAzureOpenAI | None = None, env_file_path: str | None = None, ) -> None: - """ - Initialize an AzureTextCompletion service. + """Initialize an AzureTextCompletion service. - Arguments: + Args: service_id: The service ID for the Azure deployment. (Optional) - api_key {str | None}: The optional api key. If provided, will override the value in the + api_key (str | None): The optional api key. If provided, will override the value in the env vars or .env file. - deployment_name {str | None}: The optional deployment. If provided, will override the value + deployment_name (str | None): The optional deployment. If provided, will override the value (text_deployment_name) in the env vars or .env file. - endpoint {str | None}: The optional deployment endpoint. If provided will override the value + endpoint (str | None): The optional deployment endpoint. If provided will override the value in the env vars or .env file. - base_url {str | None}: The optional deployment base_url. If provided will override the value + base_url (str | None): The optional deployment base_url. If provided will override the value in the env vars or .env file. - api_version {str | None}: The optional deployment api version. If provided will override the value + api_version (str | None): The optional deployment api version. If provided will override the value in the env vars or .env file. ad_token: The Azure Active Directory token. (Optional) ad_token_provider: The Azure Active Directory token provider. (Optional) default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - async_client {Optional[AsyncAzureOpenAI]} -- An existing client to use. (Optional) - env_file_path {str | None} -- Use the environment settings file as a fallback to + async_client (Optional[AsyncAzureOpenAI]): An existing client to use. (Optional) + env_file_path (str | None): Use the environment settings file as a fallback to environment variables. (Optional) """ azure_openai_settings = None @@ -113,15 +106,13 @@ def __init__( @classmethod def from_dict(cls, settings: dict[str, str]) -> "AzureTextCompletion": - """ - Initialize an Azure OpenAI service from a dictionary of settings. + """Initialize an Azure OpenAI service from a dictionary of settings. - Arguments: + Args: settings: A dictionary of settings for the service. should contain keys: deployment_name, endpoint, api_key and optionally: api_version, ad_auth """ - return AzureTextCompletion( service_id=settings.get("service_id"), api_key=settings.get("api_key", None), diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py index 1447e04d160e..bce92f2be560 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py @@ -9,15 +9,9 @@ from pydantic import ValidationError from semantic_kernel.connectors.ai.open_ai.const import DEFAULT_AZURE_API_VERSION -from semantic_kernel.connectors.ai.open_ai.services.azure_config_base import ( - AzureOpenAIConfigBase, -) -from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import ( - OpenAIModelTypes, -) -from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding_base import ( - OpenAITextEmbeddingBase, -) +from semantic_kernel.connectors.ai.open_ai.services.azure_config_base import AzureOpenAIConfigBase +from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import OpenAIModelTypes +from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding_base import OpenAITextEmbeddingBase from semantic_kernel.connectors.ai.open_ai.settings.azure_open_ai_settings import AzureOpenAISettings from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError from semantic_kernel.kernel_pydantic import HttpsUrl @@ -44,8 +38,7 @@ def __init__( async_client: AsyncAzureOpenAI | None = None, env_file_path: str | None = None, ) -> None: - """ - Initialize an AzureTextEmbedding service. + """Initialize an AzureTextEmbedding service. service_id: The service ID. (Optional) api_key {str | None}: The optional api key. If provided, will override the value in the @@ -63,8 +56,8 @@ def __init__( (Optional) The default value is False. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - async_client {Optional[AsyncAzureOpenAI]} -- An existing client to use. (Optional) - env_file_path {str | None} -- Use the environment settings file as a fallback to + async_client (Optional[AsyncAzureOpenAI]): An existing client to use. (Optional) + env_file_path (str | None): Use the environment settings file as a fallback to environment variables. (Optional) """ azure_openai_settings = None @@ -116,10 +109,9 @@ def __init__( @classmethod def from_dict(cls, settings: dict[str, str]) -> "AzureTextEmbedding": - """ - Initialize an Azure OpenAI service from a dictionary of settings. + """Initialize an Azure OpenAI service from a dictionary of settings. - Arguments: + Args: settings: A dictionary of settings for the service. should contain keys: deployment_name, endpoint, api_key and optionally: api_version, ad_auth diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py index f1d12a5651a9..c4ab84542d58 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py @@ -8,12 +8,8 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base import OpenAIChatCompletionBase from semantic_kernel.connectors.ai.open_ai.services.open_ai_config_base import OpenAIConfigBase -from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import ( - OpenAIModelTypes, -) -from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import ( - OpenAITextCompletionBase, -) +from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import OpenAIModelTypes +from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import OpenAITextCompletionBase from semantic_kernel.connectors.ai.open_ai.settings.open_ai_settings import OpenAISettings logger: logging.Logger = logging.getLogger(__name__) @@ -32,21 +28,20 @@ def __init__( async_client: AsyncOpenAI | None = None, env_file_path: str | None = None, ) -> None: - """ - Initialize an OpenAIChatCompletion service. + """Initialize an OpenAIChatCompletion service. - Arguments: - ai_model_id {str} -- OpenAI model name, see + Args: + ai_model_id (str): OpenAI model name, see https://platform.openai.com/docs/models - service_id {str | None} -- Service ID tied to the execution settings. - api_key {str | None} -- The optional API key to use. If provided will override, + service_id (str | None): Service ID tied to the execution settings. + api_key (str | None): The optional API key to use. If provided will override, the env vars or .env file value. - org_id {str | None} -- The optional org ID to use. If provided will override, + org_id (str | None): The optional org ID to use. If provided will override, the env vars or .env file value. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - async_client {Optional[AsyncOpenAI]} -- An existing client to use. (Optional) - env_file_path {str | None} -- Use the environment settings file as a fallback + async_client (Optional[AsyncOpenAI]): An existing client to use. (Optional) + env_file_path (str | None): Use the environment settings file as a fallback to environment variables. (Optional) """ openai_settings = None @@ -75,13 +70,11 @@ def __init__( @classmethod def from_dict(cls, settings: dict[str, str]) -> "OpenAIChatCompletion": - """ - Initialize an Open AI service from a dictionary of settings. + """Initialize an Open AI service from a dictionary of settings. - Arguments: + Args: settings: A dictionary of settings for the service. """ - return OpenAIChatCompletion( ai_model_id=settings["ai_model_id"], service_id=settings.get("service_id"), diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion_base.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion_base.py index 6fd5ee26d68a..0617f3f88169 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion_base.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion_base.py @@ -77,16 +77,15 @@ async def get_chat_message_contents( ) -> list["ChatMessageContent"]: """Executes a chat completion request and returns the result. - Arguments: - chat_history {ChatHistory} -- The chat history to use for the chat completion. - settings {OpenAIChatPromptExecutionSettings | AzureChatPromptExecutionSettings} -- The settings to use + Args: + chat_history (ChatHistory): The chat history to use for the chat completion. + settings (OpenAIChatPromptExecutionSettings | AzureChatPromptExecutionSettings): The settings to use for the chat completion request. - kwargs {Dict[str, Any]} -- The optional arguments. + kwargs (Dict[str, Any]): The optional arguments. Returns: - List[ChatMessageContent] -- The completion result(s). + List[ChatMessageContent]: The completion result(s). """ - kernel = kwargs.get("kernel", None) arguments = kwargs.get("arguments", None) if settings.function_call_behavior is not None and settings.function_call_behavior.auto_invoke_kernel_functions: @@ -154,14 +153,14 @@ async def get_streaming_chat_message_contents( ) -> AsyncGenerator[list[StreamingChatMessageContent | None], Any]: """Executes a streaming chat completion request and returns the result. - Arguments: - chat_history {ChatHistory} -- The chat history to use for the chat completion. - settings {OpenAIChatPromptExecutionSettings | AzureChatPromptExecutionSettings} -- The settings to use + Args: + chat_history (ChatHistory): The chat history to use for the chat completion. + settings (OpenAIChatPromptExecutionSettings | AzureChatPromptExecutionSettings): The settings to use for the chat completion request. - kwargs {Dict[str, Any]} -- The optional arguments. + kwargs (Dict[str, Any]): The optional arguments. Yields: - List[StreamingChatMessageContent] -- A stream of + List[StreamingChatMessageContent]: A stream of StreamingChatMessageContent when using Azure. """ kernel = kwargs.get("kernel", None) @@ -258,7 +257,7 @@ def _chat_message_content_to_dict(self, message: "ChatMessageContent") -> dict[s # region internal handlers async def _send_chat_request(self, settings: OpenAIChatPromptExecutionSettings) -> list["ChatMessageContent"]: - """Send the chat request""" + """Send the chat request.""" response = await self._send_request(request_settings=settings) response_metadata = self._get_metadata_from_chat_response(response) completions = [ @@ -269,7 +268,7 @@ async def _send_chat_request(self, settings: OpenAIChatPromptExecutionSettings) async def _send_chat_stream_request( self, settings: OpenAIChatPromptExecutionSettings ) -> AsyncGenerator[list["StreamingChatMessageContent | None"], None]: - """Send the chat stream request""" + """Send the chat stream request.""" response = await self._send_request(request_settings=settings) if not isinstance(response, AsyncStream): raise ServiceInvalidResponseError("Expected an AsyncStream[ChatCompletionChunk] response.") @@ -526,8 +525,10 @@ async def _inner_auto_function_invoke_handler(self, context: AutoFunctionInvocat except Exception as exc: logger.exception(f"Error invoking function {context.function.fully_qualified_name}: {exc}.") value = f"An error occurred while invoking the function {context.function.fully_qualified_name}: {exc}" - assert context.function_result is not None - context.function_result.value = value + if context.function_result is not None: + context.function_result.value = value + else: + context.function_result = FunctionResult(function=context.function.metadata, value=value) return diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_config_base.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_config_base.py index 17c8610f50a0..de26fd3fa94f 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_config_base.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_config_base.py @@ -32,17 +32,19 @@ def __init__( This constructor sets up a client to interact with OpenAI's API, allowing for different types of AI model interactions, like chat or text completion. - Arguments: - ai_model_id {str} -- OpenAI model identifier. Must be non-empty. + Args: + ai_model_id (str): OpenAI model identifier. Must be non-empty. Default to a preset value. - api_key {Optional[str]} -- OpenAI API key for authentication. + api_key (Optional[str]): OpenAI API key for authentication. Must be non-empty. (Optional) - ai_model_type {Optional[OpenAIModelTypes]} -- The type of OpenAI + ai_model_type (Optional[OpenAIModelTypes]): The type of OpenAI model to interact with. Defaults to CHAT. - org_id {Optional[str]} -- OpenAI organization ID. This is optional + org_id (Optional[str]): OpenAI organization ID. This is optional unless the account belongs to multiple organizations. - default_headers {Optional[Mapping[str, str]]} -- Default headers + service_id (Optional[str]): OpenAI service ID. This is optional. + default_headers (Optional[Mapping[str, str]]): Default headers for HTTP requests. (Optional) + async_client (Optional[AsyncOpenAI]): An existing OpenAI client """ # Merge APP_INFO into the headers if it exists @@ -69,9 +71,7 @@ def __init__( super().__init__(**args) def to_dict(self) -> dict[str, str]: - """ - Create a dict of the service settings. - """ + """Create a dict of the service settings.""" client_settings = { "api_key": self.client.api_key, "default_headers": {k: v for k, v in self.client.default_headers.items() if k != USER_AGENT}, diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_handler.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_handler.py index bb61c3a21cab..d70a371b3286 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_handler.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_handler.py @@ -8,16 +8,12 @@ from openai.types import Completion from openai.types.chat import ChatCompletion, ChatCompletionChunk -from semantic_kernel.connectors.ai.open_ai.exceptions.content_filter_ai_exception import ( - ContentFilterAIException, -) +from semantic_kernel.connectors.ai.open_ai.exceptions.content_filter_ai_exception import ContentFilterAIException from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import ( OpenAIEmbeddingPromptExecutionSettings, OpenAIPromptExecutionSettings, ) -from semantic_kernel.connectors.ai.open_ai.services.open_ai_model_types import ( - OpenAIModelTypes, -) +from semantic_kernel.connectors.ai.open_ai.services.open_ai_model_types import OpenAIModelTypes from semantic_kernel.exceptions import ServiceResponseException from semantic_kernel.kernel_pydantic import KernelBaseModel @@ -37,20 +33,19 @@ async def _send_request( self, request_settings: OpenAIPromptExecutionSettings, ) -> ChatCompletion | Completion | AsyncStream[ChatCompletionChunk] | AsyncStream[Completion]: - """ - Completes the given prompt. Returns a single string completion. + """Completes the given prompt. Returns a single string completion. + Cannot return multiple completions. Cannot return logprobs. - Arguments: - prompt {str} -- The prompt to complete. - messages {List[Tuple[str, str]]} -- A list of tuples, where each tuple is a role and content set. - request_settings {OpenAIPromptExecutionSettings} -- The request settings. - stream {bool} -- Whether to stream the response. + Args: + prompt (str): The prompt to complete. + messages (List[Tuple[str, str]]): A list of tuples, where each tuple is a role and content set. + request_settings (OpenAIPromptExecutionSettings): The request settings. + stream (bool): Whether to stream the response. Returns: - ChatCompletion, Completion, AsyncStream[Completion | ChatCompletionChunk] -- The completion response. + ChatCompletion, Completion, AsyncStream[Completion | ChatCompletionChunk]: The completion response. """ - try: if self.ai_model_type == OpenAIModelTypes.CHAT: response = await self.client.chat.completions.create(**request_settings.prepare_settings_dict()) @@ -88,6 +83,7 @@ async def _send_embedding_request(self, settings: OpenAIEmbeddingPromptExecution ) from ex def store_usage(self, response): + """Store the usage information from the response.""" if not isinstance(response, AsyncStream): logger.info(f"OpenAI usage: {response.usage}") self.prompt_tokens += response.usage.prompt_tokens diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py index 38051de414ec..66bc45fbed87 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py @@ -7,15 +7,9 @@ from openai import AsyncOpenAI from pydantic import ValidationError -from semantic_kernel.connectors.ai.open_ai.services.open_ai_config_base import ( - OpenAIConfigBase, -) -from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import ( - OpenAIModelTypes, -) -from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import ( - OpenAITextCompletionBase, -) +from semantic_kernel.connectors.ai.open_ai.services.open_ai_config_base import OpenAIConfigBase +from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import OpenAIModelTypes +from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import OpenAITextCompletionBase from semantic_kernel.connectors.ai.open_ai.settings.open_ai_settings import OpenAISettings logger: logging.Logger = logging.getLogger(__name__) @@ -34,21 +28,20 @@ def __init__( async_client: AsyncOpenAI | None = None, env_file_path: str | None = None, ) -> None: - """ - Initialize an OpenAITextCompletion service. + """Initialize an OpenAITextCompletion service. - Arguments: - ai_model_id {str | None} -- OpenAI model name, see + Args: + ai_model_id (str | None): OpenAI model name, see https://platform.openai.com/docs/models - service_id {str | None} -- Service ID tied to the execution settings. - api_key {str | None} -- The optional API key to use. If provided will override, + service_id (str | None): Service ID tied to the execution settings. + api_key (str | None): The optional API key to use. If provided will override, the env vars or .env file value. - org_id {str | None} -- The optional org ID to use. If provided will override, + org_id (str | None): The optional org ID to use. If provided will override, the env vars or .env file value. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) - async_client {Optional[AsyncOpenAI]} -- An existing client to use. (Optional) - env_file_path {str | None} -- Use the environment settings file as a fallback to + async_client (Optional[AsyncOpenAI]): An existing client to use. (Optional) + env_file_path (str | None): Use the environment settings file as a fallback to environment variables. (Optional) """ try: @@ -76,10 +69,9 @@ def __init__( @classmethod def from_dict(cls, settings: dict[str, str]) -> "OpenAITextCompletion": - """ - Initialize an Open AI service from a dictionary of settings. + """Initialize an Open AI service from a dictionary of settings. - Arguments: + Args: settings: A dictionary of settings for the service. """ if "default_headers" in settings and isinstance(settings["default_headers"], str): diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion_base.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion_base.py index b95396183653..6be5147dc6ea 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion_base.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion_base.py @@ -39,12 +39,12 @@ async def get_text_contents( ) -> list["TextContent"]: """Executes a completion request and returns the result. - Arguments: - prompt {str} -- The prompt to use for the completion request. - settings {OpenAITextPromptExecutionSettings} -- The settings to use for the completion request. + Args: + prompt (str): The prompt to use for the completion request. + settings (OpenAITextPromptExecutionSettings): The settings to use for the completion request. Returns: - List["TextContent"] -- The completion result(s). + List["TextContent"]: The completion result(s). """ if isinstance(settings, OpenAITextPromptExecutionSettings): settings.prompt = prompt @@ -78,16 +78,16 @@ async def get_streaming_text_contents( prompt: str, settings: "OpenAIPromptExecutionSettings", ) -> AsyncGenerator[list["StreamingTextContent"], Any]: - """ - Executes a completion request and streams the result. + """Executes a completion request and streams the result. + Supports both chat completion and text completion. - Arguments: - prompt {str} -- The prompt to use for the completion request. - settings {OpenAITextPromptExecutionSettings} -- The settings to use for the completion request. + Args: + prompt (str): The prompt to use for the completion request. + settings (OpenAITextPromptExecutionSettings): The settings to use for the completion request. Yields: - List["StreamingTextContent"] -- The result stream made up of StreamingTextContent objects. + List["StreamingTextContent"]: The result stream made up of StreamingTextContent objects. """ if "prompt" in settings.model_fields: settings.prompt = prompt diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py index f3b140f60b2d..4529bb50e7ff 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py @@ -6,15 +6,9 @@ from openai import AsyncOpenAI from pydantic import ValidationError -from semantic_kernel.connectors.ai.open_ai.services.open_ai_config_base import ( - OpenAIConfigBase, -) -from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import ( - OpenAIModelTypes, -) -from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding_base import ( - OpenAITextEmbeddingBase, -) +from semantic_kernel.connectors.ai.open_ai.services.open_ai_config_base import OpenAIConfigBase +from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import OpenAIModelTypes +from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding_base import OpenAITextEmbeddingBase from semantic_kernel.connectors.ai.open_ai.settings.open_ai_settings import OpenAISettings from semantic_kernel.utils.experimental_decorator import experimental_class @@ -35,21 +29,20 @@ def __init__( async_client: AsyncOpenAI | None = None, env_file_path: str | None = None, ) -> None: - """ - Initializes a new instance of the OpenAITextCompletion class. + """Initializes a new instance of the OpenAITextCompletion class. - Arguments: - ai_model_id {str} -- OpenAI model name, see + Args: + ai_model_id (str): OpenAI model name, see https://platform.openai.com/docs/models - service_id {str | None} -- Service ID tied to the execution settings. - api_key {str | None} -- The optional API key to use. If provided will override, + service_id (str | None): Service ID tied to the execution settings. + api_key (str | None): The optional API key to use. If provided will override, the env vars or .env file value. - org_id {str | None} -- The optional org ID to use. If provided will override, + org_id (str | None): The optional org ID to use. If provided will override, the env vars or .env file value. - default_headers {Optional[Mapping[str,str]]}: The default headers mapping of string keys to + default_headers (Mapping[str,str] | None): The default headers mapping of string keys to string values for HTTP requests. (Optional) - async_client {Optional[AsyncOpenAI]} -- An existing client to use. (Optional) - env_file_path {str | None} -- Use the environment settings file as + async_client (Optional[AsyncOpenAI]): An existing client to use. (Optional) + env_file_path (str | None): Use the environment settings file as a fallback to environment variables. (Optional) """ try: @@ -77,13 +70,11 @@ def __init__( @classmethod def from_dict(cls, settings: dict[str, str]) -> "OpenAITextEmbedding": - """ - Initialize an Open AI service from a dictionary of settings. + """Initialize an Open AI service from a dictionary of settings. - Arguments: + Args: settings: A dictionary of settings for the service. """ - return OpenAITextEmbedding( ai_model_id=settings["ai_model_id"], api_key=settings.get("api_key", None), diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding_base.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding_base.py index cc673be076c8..00b2dd180603 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding_base.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding_base.py @@ -1,9 +1,15 @@ # Copyright (c) Microsoft. All rights reserved. +import sys from typing import Any from numpy import array, ndarray +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + from semantic_kernel.connectors.ai.embeddings.embedding_generator_base import EmbeddingGeneratorBase from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import ( OpenAIEmbeddingPromptExecutionSettings, @@ -15,19 +21,8 @@ @experimental_class class OpenAITextEmbeddingBase(OpenAIHandler, EmbeddingGeneratorBase): + @override async def generate_embeddings(self, texts: list[str], batch_size: int | None = None, **kwargs: Any) -> ndarray: - """Generates embeddings for the given texts. - - Arguments: - texts {List[str]} -- The texts to generate embeddings for. - batch_size {Optional[int]} -- The batch size to use for the request. - kwargs {Dict[str, Any]} -- Additional arguments to pass to the request, - see OpenAIEmbeddingPromptExecutionSettings for the details. - - Returns: - ndarray -- The embeddings for the text. - - """ settings = OpenAIEmbeddingPromptExecutionSettings( ai_model_id=self.ai_model_id, **kwargs, @@ -43,5 +38,6 @@ async def generate_embeddings(self, texts: list[str], batch_size: int | None = N raw_embeddings.extend(raw_embedding) return array(raw_embeddings) + @override def get_prompt_execution_settings_class(self) -> PromptExecutionSettings: return OpenAIEmbeddingPromptExecutionSettings diff --git a/python/semantic_kernel/connectors/ai/open_ai/settings/azure_open_ai_settings.py b/python/semantic_kernel/connectors/ai/open_ai/settings/azure_open_ai_settings.py index 27ecc718d12b..3a59d707fa9e 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/settings/azure_open_ai_settings.py +++ b/python/semantic_kernel/connectors/ai/open_ai/settings/azure_open_ai_settings.py @@ -8,7 +8,7 @@ class AzureOpenAISettings(BaseSettings): - """AzureOpenAI model settings + """AzureOpenAI model settings. The settings are first loaded from environment variables with the prefix 'AZURE_OPENAI_'. If the environment variables are not found, the settings can be loaded from a .env file @@ -64,6 +64,8 @@ class AzureOpenAISettings(BaseSettings): api_version: str | None = None class Config: + """Pydantic configuration settings.""" + env_prefix = "AZURE_OPENAI_" env_file = None env_file_encoding = "utf-8" @@ -72,6 +74,7 @@ class Config: @classmethod def create(cls, **kwargs): + """Create an instance of the class.""" if "env_file_path" in kwargs and kwargs["env_file_path"]: cls.Config.env_file = kwargs["env_file_path"] else: diff --git a/python/semantic_kernel/connectors/ai/open_ai/settings/open_ai_settings.py b/python/semantic_kernel/connectors/ai/open_ai/settings/open_ai_settings.py index 789829655363..a4de3e11bae5 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/settings/open_ai_settings.py +++ b/python/semantic_kernel/connectors/ai/open_ai/settings/open_ai_settings.py @@ -5,7 +5,7 @@ class OpenAISettings(BaseSettings): - """OpenAI model settings + """OpenAI model settings. The settings are first loaded from environment variables with the prefix 'OPENAI_'. If the environment variables are not found, the settings can be loaded from a .env file with the @@ -34,6 +34,8 @@ class OpenAISettings(BaseSettings): embedding_model_id: str | None = None class Config: + """Pydantic configuration settings.""" + env_prefix = "OPENAI_" env_file = None env_file_encoding = "utf-8" @@ -42,6 +44,7 @@ class Config: @classmethod def create(cls, **kwargs): + """Create an instance of the class.""" if "env_file_path" in kwargs and kwargs["env_file_path"]: cls.Config.env_file = kwargs["env_file_path"] else: diff --git a/python/semantic_kernel/connectors/ai/prompt_execution_settings.py b/python/semantic_kernel/connectors/ai/prompt_execution_settings.py index e607c03cc8d7..c8036a182ddb 100644 --- a/python/semantic_kernel/connectors/ai/prompt_execution_settings.py +++ b/python/semantic_kernel/connectors/ai/prompt_execution_settings.py @@ -15,11 +15,10 @@ class PromptExecutionSettings(KernelBaseModel): create a generic PromptExecutionSettings object in your application, which gets mapped into the keys of the prompt execution settings that each services returns by using the service.get_prompt_execution_settings() method. - Parameters: - service_id (str): The service ID to use for the request. - extension_data (Dict[str, Any], optional): Any additional data to send with the request. Defaults to None. - kwargs (Any): Additional keyword arguments, - these are attempted to parse into the keys of the specific prompt execution settings. + Attributes: + service_id (str | None): The service ID to use for the request. + extension_data (Dict[str, Any]): Any additional data to send with the request. + Methods: prepare_settings_dict: Prepares the settings as a dictionary for sending to the AI service. update_from_prompt_execution_settings: Update the keys from another prompt execution settings object. @@ -30,6 +29,13 @@ class PromptExecutionSettings(KernelBaseModel): extension_data: dict[str, Any] = Field(default_factory=dict) def __init__(self, service_id: str | None = None, **kwargs: Any): + """Initialize the prompt execution settings. + + Args: + service_id (str): The service ID to use for the request. + kwargs (Any): Additional keyword arguments, + these are attempted to parse into the keys of the specific prompt execution settings. + """ extension_data = kwargs.pop("extension_data", {}) extension_data.update(kwargs) super().__init__(service_id=service_id, extension_data=extension_data) diff --git a/python/semantic_kernel/connectors/ai/text_completion_client_base.py b/python/semantic_kernel/connectors/ai/text_completion_client_base.py index 560fde2eb89a..af9a7c65c2c8 100644 --- a/python/semantic_kernel/connectors/ai/text_completion_client_base.py +++ b/python/semantic_kernel/connectors/ai/text_completion_client_base.py @@ -20,15 +20,14 @@ async def get_text_contents( prompt: str, settings: "PromptExecutionSettings", ) -> list["TextContent"]: - """ - This is the method that is called from the kernel to get a response from a text-optimized LLM. + """This is the method that is called from the kernel to get a response from a text-optimized LLM. - Arguments: - prompt {str} -- The prompt to send to the LLM. - settings {PromptExecutionSettings} -- Settings for the request. + Args: + prompt (str): The prompt to send to the LLM. + settings (PromptExecutionSettings): Settings for the request. - Returns: - list[TextContent] -- A string or list of strings representing the response(s) from the LLM. + Returns: + list[TextContent]: A string or list of strings representing the response(s) from the LLM. """ @abstractmethod @@ -37,14 +36,13 @@ def get_streaming_text_contents( prompt: str, settings: "PromptExecutionSettings", ) -> AsyncGenerator[list["StreamingTextContent"], Any]: - """ - This is the method that is called from the kernel to get a stream response from a text-optimized LLM. + """This is the method that is called from the kernel to get a stream response from a text-optimized LLM. - Arguments: - prompt {str} -- The prompt to send to the LLM. - settings {PromptExecutionSettings} -- Settings for the request. + Args: + prompt (str): The prompt to send to the LLM. + settings (PromptExecutionSettings): Settings for the request. Yields: - list[StreamingTextContent] -- A stream representing the response(s) from the LLM. + list[StreamingTextContent]: A stream representing the response(s) from the LLM. """ ... diff --git a/python/semantic_kernel/connectors/memory/astradb/astra_client.py b/python/semantic_kernel/connectors/memory/astradb/astra_client.py index 818409d08691..d39c6b8254bc 100644 --- a/python/semantic_kernel/connectors/memory/astradb/astra_client.py +++ b/python/semantic_kernel/connectors/memory/astradb/astra_client.py @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft. All rights reserved. + import json import aiohttp @@ -27,6 +29,7 @@ def __init__( similarity_function: str, session: aiohttp.ClientSession | None = None, ): + """Initializes a new instance of the AstraClient class.""" self.astra_id = astra_id self.astra_application_token = astra_application_token self.astra_region = astra_region @@ -57,11 +60,13 @@ async def _run_query(self, request_url: str, query: dict): raise ServiceResponseException(f"Astra DB not available. Status : {response}") async def find_collections(self, include_detail: bool = True): + """Finds all collections in the keyspace.""" query = {"findCollections": {"options": {"explain": include_detail}}} result = await self._run_query(self.request_base_url, query) return result["status"]["collections"] async def find_collection(self, collection_name: str): + """Finds a collection in the keyspace.""" collections = await self.find_collections(False) found = False for collection in collections: @@ -76,6 +81,7 @@ async def create_collection( embedding_dim: int | None = None, similarity_function: str | None = None, ): + """Creates a new collection in the keyspace.""" query = { "createCollection": { "name": collection_name, @@ -91,6 +97,7 @@ async def create_collection( return True if result["status"]["ok"] == 1 else False async def delete_collection(self, collection_name: str): + """Deletes a collection from the keyspace.""" query = {"deleteCollection": {"name": collection_name}} result = await self._run_query(self.request_base_url, query) return True if result["status"]["ok"] == 1 else False @@ -107,6 +114,7 @@ async def find_documents( include_vector: bool | None = None, include_similarity: bool | None = None, ) -> list[dict]: + """Finds all documents in the collection.""" find_query = {} if filter is not None: @@ -132,16 +140,19 @@ async def find_documents( return result["data"]["documents"] async def insert_document(self, collection_name: str, document: dict) -> str: + """Inserts a document into the collection.""" query = {"insertOne": {"document": document}} result = await self._run_query(self._build_request_collection_url(collection_name), query) return result["status"]["insertedIds"][0] async def insert_documents(self, collection_name: str, documents: list[dict]) -> list[str]: + """Inserts multiple documents into the collection.""" query = {"insertMany": {"documents": documents}} result = await self._run_query(self._build_request_collection_url(collection_name), query) return result["status"]["insertedIds"] async def update_document(self, collection_name: str, filter: dict, update: dict, upsert: bool = True) -> dict: + """Updates a document in the collection.""" query = { "findOneAndUpdate": { "filter": filter, @@ -153,6 +164,7 @@ async def update_document(self, collection_name: str, filter: dict, update: dict return result["status"] async def update_documents(self, collection_name: str, filter: dict, update: dict): + """Updates multiple documents in the collection.""" query = { "updateMany": { "filter": filter, @@ -163,6 +175,7 @@ async def update_documents(self, collection_name: str, filter: dict, update: dic return result["status"] async def delete_documents(self, collection_name: str, filter: dict) -> int: + """Deletes documents from the collection.""" query = {"deleteMany": {"filter": filter}} result = await self._run_query(self._build_request_collection_url(collection_name), query) return result["status"]["deletedCount"] diff --git a/python/semantic_kernel/connectors/memory/astradb/astradb_memory_store.py b/python/semantic_kernel/connectors/memory/astradb/astradb_memory_store.py index 1d883be95cf2..a48995a599f8 100644 --- a/python/semantic_kernel/connectors/memory/astradb/astradb_memory_store.py +++ b/python/semantic_kernel/connectors/memory/astradb/astradb_memory_store.py @@ -2,17 +2,20 @@ import asyncio import logging +import sys import aiohttp from numpy import ndarray from pydantic import ValidationError +if sys.version_info >= (3, 12): + pass +else: + pass + from semantic_kernel.connectors.memory.astradb.astra_client import AstraClient from semantic_kernel.connectors.memory.astradb.astradb_settings import AstraDBSettings -from semantic_kernel.connectors.memory.astradb.utils import ( - build_payload, - parse_payload, -) +from semantic_kernel.connectors.memory.astradb.utils import build_payload, parse_payload from semantic_kernel.exceptions import MemoryConnectorInitializationError from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase @@ -45,15 +48,15 @@ def __init__( ) -> None: """Initializes a new instance of the AstraDBMemoryStore class. - Arguments: - astra_application_token {str} -- The Astra application token. - astra_id {str} -- The Astra id of database. - astra_region {str} -- The Astra region - keyspace_name {str} -- The Astra keyspace - embedding_dim {int} -- The dimensionality to use for new collections. - similarity {str} -- TODO - session -- Optional session parameter - env_file_path {str | None} -- Use the environment settings file as a + Args: + astra_application_token (str): The Astra application token. + astra_id (str): The Astra id of database. + astra_region (str): The Astra region + keyspace_name (str): The Astra keyspace + embedding_dim (int): The dimensionality to use for new collections. + similarity (str): TODO + session: Optional session parameter + env_file_path (str | None): Use the environment settings file as a fallback to environment variables. (Optional) """ astradb_settings = None @@ -66,17 +69,21 @@ def __init__( astra_application_token = astra_application_token or ( astradb_settings.app_token.get_secret_value() if astradb_settings and astradb_settings.app_token else None ) - assert astra_application_token is not None, "The astra_application_token cannot be None." + if astra_application_token is None: + raise ValueError("The astra_application_token cannot be None.") astra_id = astra_id or (astradb_settings.db_id if astradb_settings and astradb_settings.db_id else None) - assert astra_id is not None, "The astra_id cannot be None." + if astra_id is None: + raise ValueError("The astra_id cannot be None.") astra_region = astra_region or ( astradb_settings.region if astradb_settings and astradb_settings.region else None ) - assert astra_region is not None, "The astra_region cannot be None." + if astra_region is None: + raise ValueError("The astra_region cannot be None.") keyspace_name = keyspace_name or ( astradb_settings.keyspace if astradb_settings and astradb_settings.keyspace else None ) - assert keyspace_name is not None, "The keyspace_name cannot be None." + if keyspace_name is None: + raise ValueError("The keyspace_name cannot be None.") self._embedding_dim = embedding_dim self._similarity = similarity @@ -102,7 +109,7 @@ async def get_collections(self) -> list[str]: """Gets the list of collections. Returns: - List[str] -- The list of collections. + List[str]: The list of collections. """ return await self._client.find_collections(False) @@ -114,11 +121,12 @@ async def create_collection( ) -> None: """Creates a new collection in Astra if it does not exist. - Arguments: - collection_name {str} -- The name of the collection to create. - dimension_num {int} -- The dimension of the vectors to be stored in this collection. - distance_type {str} -- Specifies the similarity metric to be used when querying or comparing vectors within + Args: + collection_name (str): The name of the collection to create. + dimension_num (int): The dimension of the vectors to be stored in this collection. + distance_type (str): Specifies the similarity metric to be used when querying or comparing vectors within this collection. The available options are dot_product, euclidean, and cosine. + Returns: None """ @@ -137,8 +145,8 @@ async def create_collection( async def delete_collection(self, collection_name: str) -> None: """Deletes a collection. - Arguments: - collection_name {str} -- The name of the collection to delete. + Args: + collection_name (str): The name of the collection to delete. Returns: None @@ -152,25 +160,27 @@ async def delete_collection(self, collection_name: str) -> None: async def does_collection_exist(self, collection_name: str) -> bool: """Checks if a collection exists. - Arguments: - collection_name {str} -- The name of the collection to check. + Args: + collection_name (str): The name of the collection to check. Returns: - bool -- True if the collection exists; otherwise, False. + bool: True if the collection exists; otherwise, False. """ return await self._client.find_collection(collection_name) async def upsert(self, collection_name: str, record: MemoryRecord) -> str: - """Upserts a memory record into the data store. Does not guarantee that the collection exists. - If the record already exists, it will be updated. - If the record does not exist, it will be created. + """Upsert a memory record into the data store. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - record {MemoryRecord} -- The memory record to upsert. + Does not guarantee that the collection exists. + If the record already exists, it will be updated. + If the record does not exist, it will be created. + + Args: + collection_name (str): The name associated with a collection of embeddings. + record (MemoryRecord): The memory record to upsert. Returns: - str -- The unique identifier for the memory record. + str: The unique identifier for the memory record. """ filter = {"_id": record._id} update = {"$set": build_payload(record)} @@ -179,29 +189,31 @@ async def upsert(self, collection_name: str, record: MemoryRecord) -> str: return status["upsertedId"] if "upsertedId" in status else record._id async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: - """Upserts a batch of memory records into the data store. Does not guarantee that the collection exists. - If the record already exists, it will be updated. - If the record does not exist, it will be created. + """Upsert a batch of memory records into the data store. + + Does not guarantee that the collection exists. + If the record already exists, it will be updated. + If the record does not exist, it will be created. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - records {List[MemoryRecord]} -- The memory records to upsert. + Args: + collection_name (str): The name associated with a collection of embeddings. + records (List[MemoryRecord]): The memory records to upsert. Returns: - List[str] -- The unique identifiers for the memory record. + List[str]: The unique identifiers for the memory record. """ return await asyncio.gather(*[self.upsert(collection_name, record) for record in records]) async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord: """Gets a record. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name of the collection to get the record from. - key {str} -- The unique database key of the record. - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the record from. + key (str): The unique database key of the record. + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - MemoryRecord -- The record. + MemoryRecord: The record. """ filter = {"_id": key} documents = await self._client.find_documents( @@ -220,15 +232,14 @@ async def get_batch( ) -> list[MemoryRecord]: """Gets a batch of records. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name of the collection to get the records from. - keys {List[str]} -- The unique database keys of the records. - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + Args: + collection_name (str): The name of the collection to get the records from. + keys (List[str]): The unique database keys of the records. + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[MemoryRecord] -- The records. + List[MemoryRecord]: The records. """ - filter = {"_id": {"$in": keys}} documents = await self._client.find_documents( collection_name=collection_name, @@ -240,9 +251,9 @@ async def get_batch( async def remove(self, collection_name: str, key: str) -> None: """Removes a memory record from the data store. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name of the collection to remove the record from. - key {str} -- The unique id associated with the memory record to remove. + Args: + collection_name (str): The name of the collection to remove the record from. + key (str): The unique id associated with the memory record to remove. Returns: None @@ -253,9 +264,9 @@ async def remove(self, collection_name: str, key: str) -> None: async def remove_batch(self, collection_name: str, keys: list[str]) -> None: """Removes a batch of records. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name of the collection to remove the records from. - keys {List[str]} -- The unique ids associated with the memory records to remove. + Args: + collection_name (str): The name of the collection to remove the records from. + keys (List[str]): The unique ids associated with the memory records to remove. Returns: None @@ -271,14 +282,15 @@ async def get_nearest_match( with_embedding: bool = False, ) -> tuple[MemoryRecord, float]: """Gets the nearest match to an embedding using cosine similarity. - Arguments: - collection_name {str} -- The name of the collection to get the nearest matches from. - embedding {ndarray} -- The embedding to find the nearest matches to. - min_relevance_score {float} -- The minimum relevance score of the matches. (default: {0.0}) - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + + Args: + collection_name (str): The name of the collection to get the nearest matches from. + embedding (ndarray): The embedding to find the nearest matches to. + min_relevance_score (float): The minimum relevance score of the matches. (default: {0.0}) + with_embedding (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - Tuple[MemoryRecord, float] -- The record and the relevance score. + Tuple[MemoryRecord, float]: The record and the relevance score. """ matches = await self.get_nearest_matches( collection_name=collection_name, @@ -298,15 +310,16 @@ async def get_nearest_matches( with_embeddings: bool = False, ) -> list[tuple[MemoryRecord, float]]: """Gets the nearest matches to an embedding using cosine similarity. - Arguments: - collection_name {str} -- The name of the collection to get the nearest matches from. - embedding {ndarray} -- The embedding to find the nearest matches to. - limit {int} -- The maximum number of matches to return. - min_relevance_score {float} -- The minimum relevance score of the matches. (default: {0.0}) - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + + Args: + collection_name (str): The name of the collection to get the nearest matches from. + embedding (ndarray): The embedding to find the nearest matches to. + limit (int): The maximum number of matches to return. + min_relevance_score (float): The minimum relevance score of the matches. (default: {0.0}) + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[Tuple[MemoryRecord, float]] -- The records and their relevance scores. + List[Tuple[MemoryRecord, float]]: The records and their relevance scores. """ matches = await self._client.find_documents( collection_name=collection_name, diff --git a/python/semantic_kernel/connectors/memory/astradb/astradb_settings.py b/python/semantic_kernel/connectors/memory/astradb/astradb_settings.py index 44b39a50dfd1..18fa062735e1 100644 --- a/python/semantic_kernel/connectors/memory/astradb/astradb_settings.py +++ b/python/semantic_kernel/connectors/memory/astradb/astradb_settings.py @@ -8,7 +8,7 @@ @experimental_class class AstraDBSettings(BaseModelSettings): - """AstraDB model settings + """AstraDB model settings. Optional: - app_token: SecretStr | None - AstraDB token @@ -27,4 +27,6 @@ class AstraDBSettings(BaseModelSettings): keyspace: str class Config(BaseModelSettings.Config): + """Pydantic configuration settings.""" + env_prefix = "ASTRADB_" diff --git a/python/semantic_kernel/connectors/memory/astradb/utils.py b/python/semantic_kernel/connectors/memory/astradb/utils.py index d3d7f19ae97f..3cc834ac2096 100644 --- a/python/semantic_kernel/connectors/memory/astradb/utils.py +++ b/python/semantic_kernel/connectors/memory/astradb/utils.py @@ -9,19 +9,20 @@ class AsyncSession: def __init__(self, session: aiohttp.ClientSession = None): + """Initializes a new instance of the AsyncSession class.""" self._session = session if session else aiohttp.ClientSession() async def __aenter__(self): + """Enter the session.""" return await self._session.__aenter__() async def __aexit__(self, *args, **kwargs): + """Close the session.""" await self._session.close() def build_payload(record: MemoryRecord) -> dict[str, Any]: - """ - Builds a metadata payload to be sent to AstraDb from a MemoryRecord. - """ + """Builds a metadata payload to be sent to AstraDb from a MemoryRecord.""" payload: dict[str, Any] = {} payload["$vector"] = record.embedding.tolist() if record._text: @@ -34,9 +35,7 @@ def build_payload(record: MemoryRecord) -> dict[str, Any]: def parse_payload(document: dict[str, Any]) -> MemoryRecord: - """ - Parses a record from AstraDb into a MemoryRecord. - """ + """Parses a record from AstraDb into a MemoryRecord.""" text = document.get("text", None) description = document["description"] if "description" in document else None additional_metadata = document["additional_metadata"] if "additional_metadata" in document else None diff --git a/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_ai_search_settings.py b/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_ai_search_settings.py index b2fd2f7cb456..76edcd688c18 100644 --- a/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_ai_search_settings.py +++ b/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_ai_search_settings.py @@ -9,9 +9,9 @@ @experimental_class class AzureAISearchSettings(BaseModelSettings): - """Azure AI Search model settings currently used by the AzureCognitiveSearchMemoryStore connector + """Azure AI Search model settings currently used by the AzureCognitiveSearchMemoryStore connector. - Optional: + Args: - api_key: SecretStr - Azure AI Search API key (Env var AZURE_AI_SEARCH_API_KEY) - endpoint: HttpsUrl - Azure AI Search endpoint (Env var AZURE_AI_SEARCH_ENDPOINT) - index_name: str - Azure AI Search index name (Env var AZURE_AI_SEARCH_INDEX_NAME) @@ -22,12 +22,12 @@ class AzureAISearchSettings(BaseModelSettings): index_name: str | None = None class Config(BaseModelSettings.Config): + """Pydantic configuration settings.""" + env_prefix = "AZURE_AI_SEARCH_" def model_dump(self): - """ - Custom method to dump model data in the required format. - """ + """Custom method to dump model data in the required format.""" return { "api_key": self.api_key.get_secret_value() if self.api_key else None, "endpoint": str(self.endpoint), diff --git a/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py b/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py index 5d0007dab1c9..79765332a900 100644 --- a/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py +++ b/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py @@ -53,15 +53,15 @@ def __init__( ) -> None: """Initializes a new instance of the AzureCognitiveSearchMemoryStore class. - Arguments: - vector_size {int} -- Embedding vector size. - search_endpoint {str | None} -- The endpoint of the Azure Cognitive Search service + Args: + vector_size (int): Embedding vector size. + search_endpoint (str | None): The endpoint of the Azure Cognitive Search service (default: {None}). - admin_key {str | None} -- Azure Cognitive Search API key (default: {None}). - azure_credentials {AzureKeyCredential | None} -- Azure Cognitive Search credentials (default: {None}). - token_credentials {TokenCredential | None} -- Azure Cognitive Search token credentials + admin_key (str | None): Azure Cognitive Search API key (default: {None}). + azure_credentials (AzureKeyCredential | None): Azure Cognitive Search credentials (default: {None}). + token_credentials (TokenCredential | None): Azure Cognitive Search token credentials (default: {None}). - env_file_path {str | None} -- Use the environment settings file as a fallback + env_file_path (str | None): Use the environment settings file as a fallback to environment variables Instantiate using Async Context Manager: @@ -84,7 +84,8 @@ def __init__( search_endpoint = search_endpoint or ( acs_memory_settings.endpoint if acs_memory_settings and acs_memory_settings.endpoint else None ) - assert search_endpoint, "The ACS endpoint is required to connect to Azure Cognitive Search." + if not search_endpoint: + raise ValueError("The ACS endpoint is required to connect to Azure Cognitive Search.") self._vector_size = vector_size self._search_index_client = get_search_index_async_client( @@ -92,7 +93,7 @@ def __init__( ) async def close(self): - """Async close connection, invoked by MemoryStoreBase.__aexit__()""" + """Async close connection, invoked by MemoryStoreBase.__aexit__().""" if self._search_index_client is not None: await self._search_index_client.close() @@ -104,18 +105,17 @@ async def create_collection( ) -> None: """Creates a new collection if it does not exist. - Arguments: - collection_name {str} -- The name of the collection to create. - vector_config {HnswVectorSearchAlgorithmConfiguration} -- Optional search algorithm configuration + Args: + collection_name (str): The name of the collection to create. + vector_config (HnswVectorSearchAlgorithmConfiguration): Optional search algorithm configuration (default: {None}). - semantic_config {SemanticConfiguration} -- Optional search index configuration (default: {None}). - search_resource_encryption_key {SearchResourceEncryptionKey} -- Optional Search Encryption Key + semantic_config (SemanticConfiguration): Optional search index configuration (default: {None}). + search_resource_encryption_key (SearchResourceEncryptionKey): Optional Search Encryption Key (default: {None}). Returns: None """ - vector_search_profile_name = "az-vector-config" if vector_config: vector_search_profile = VectorSearchProfile( @@ -168,9 +168,8 @@ async def get_collections(self) -> list[str]: """Gets the list of collections. Returns: - List[str] -- The list of collections. + List[str]: The list of collections. """ - results_list = [] items = self._search_index_client.list_index_names() if isawaitable(items): @@ -184,8 +183,8 @@ async def get_collections(self) -> list[str]: async def delete_collection(self, collection_name: str) -> None: """Deletes a collection. - Arguments: - collection_name {str} -- The name of the collection to delete. + Args: + collection_name (str): The name of the collection to delete. Returns: None @@ -195,13 +194,12 @@ async def delete_collection(self, collection_name: str) -> None: async def does_collection_exist(self, collection_name: str) -> bool: """Checks if a collection exists. - Arguments: - collection_name {str} -- The name of the collection to check. + Args: + collection_name (str): The name of the collection to check. Returns: - bool -- True if the collection exists; otherwise, False. + bool: True if the collection exists; otherwise, False. """ - try: collection_result = await self._search_index_client.get_index(name=collection_name.lower()) @@ -215,14 +213,13 @@ async def does_collection_exist(self, collection_name: str) -> bool: async def upsert(self, collection_name: str, record: MemoryRecord) -> str: """Upsert a record. - Arguments: - collection_name {str} -- The name of the collection to upsert the record into. - record {MemoryRecord} -- The record to upsert. + Args: + collection_name (str): The name of the collection to upsert the record into. + record (MemoryRecord): The record to upsert. Returns: - str -- The unique record id of the record. + str: The unique record id of the record. """ - result = await self.upsert_batch(collection_name, [record]) if result: return result[0] @@ -231,14 +228,13 @@ async def upsert(self, collection_name: str, record: MemoryRecord) -> str: async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: """Upsert a batch of records. - Arguments: - collection_name {str} -- The name of the collection to upsert the records into. - records {List[MemoryRecord]} -- The records to upsert. + Args: + collection_name (str): The name of the collection to upsert the records into. + records (List[MemoryRecord]): The records to upsert. Returns: - List[str] -- The unique database keys of the records. + List[str]: The unique database keys of the records. """ - # Initialize search client here # Look up Search client class to see if exists or create search_client = self._search_index_client.get_search_client(collection_name.lower()) @@ -268,15 +264,14 @@ async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord: """Gets a record. - Arguments: - collection_name {str} -- The name of the collection to get the record from. - key {str} -- The unique database key of the record. - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the record from. + key (str): The unique database key of the record. + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - MemoryRecord -- The record. + MemoryRecord: The record. """ - # Look up Search client class to see if exists or create search_client = self._search_index_client.get_search_client(collection_name.lower()) @@ -298,15 +293,14 @@ async def get_batch( ) -> list[MemoryRecord]: """Gets a batch of records. - Arguments: - collection_name {str} -- The name of the collection to get the records from. - keys {List[str]} -- The unique database keys of the records. - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + Args: + collection_name (str): The name of the collection to get the records from. + keys (List[str]): The unique database keys of the records. + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[MemoryRecord] -- The records. + List[MemoryRecord]: The records. """ - search_results = [] for key in keys: @@ -322,28 +316,26 @@ async def get_batch( async def remove_batch(self, collection_name: str, keys: list[str]) -> None: """Removes a batch of records. - Arguments: - collection_name {str} -- The name of the collection to remove the records from. - keys {List[str]} -- The unique database keys of the records to remove. + Args: + collection_name (str): The name of the collection to remove the records from. + keys (List[str]): The unique database keys of the records to remove. Returns: None """ - for record_id in keys: await self.remove(collection_name=collection_name.lower(), key=encode_id(record_id)) async def remove(self, collection_name: str, key: str) -> None: """Removes a record. - Arguments: - collection_name {str} -- The name of the collection to remove the record from. - key {str} -- The unique database key of the record to remove. + Args: + collection_name (str): The name of the collection to remove the record from. + key (str): The unique database key of the record to remove. Returns: None """ - # Look up Search client class to see if exists or create search_client = self._search_index_client.get_search_client(collection_name.lower()) docs_to_delete = {SEARCH_FIELD_ID: encode_id(key)} @@ -360,16 +352,15 @@ async def get_nearest_match( ) -> tuple[MemoryRecord, float]: """Gets the nearest match to an embedding using vector configuration parameters. - Arguments: - collection_name {str} -- The name of the collection to get the nearest match from. - embedding {ndarray} -- The embedding to find the nearest match to. - min_relevance_score {float} -- The minimum relevance score of the match. (default: {0.0}) - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the nearest match from. + embedding (ndarray): The embedding to find the nearest match to. + min_relevance_score (float): The minimum relevance score of the match. (default: {0.0}) + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - Tuple[MemoryRecord, float] -- The record and the relevance score. + Tuple[MemoryRecord, float]: The record and the relevance score. """ - memory_records = await self.get_nearest_matches( collection_name=collection_name, embedding=embedding, @@ -394,16 +385,15 @@ async def get_nearest_matches( """Gets the nearest matches to an embedding using vector configuration. Parameters: - collection_name (str) -- The name of the collection to get the nearest matches from. - embedding (ndarray) -- The embedding to find the nearest matches to. - limit {int} -- The maximum number of matches to return. - min_relevance_score {float} -- The minimum relevance score of the matches. (default: {0.0}) - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + collection_name (str) : The name of the collection to get the nearest matches from. + embedding (ndarray) : The embedding to find the nearest matches to. + limit (int): The maximum number of matches to return. + min_relevance_score (float): The minimum relevance score of the matches. (default: {0.0}) + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[Tuple[MemoryRecord, float]] -- The records and their relevance scores. + List[Tuple[MemoryRecord, float]]: The records and their relevance scores. """ - # Look up Search client class to see if exists or create search_client = self._search_index_client.get_search_client(collection_name.lower()) diff --git a/python/semantic_kernel/connectors/memory/azure_cognitive_search/utils.py b/python/semantic_kernel/connectors/memory/azure_cognitive_search/utils.py index 43893d750d81..54f53935f83c 100644 --- a/python/semantic_kernel/connectors/memory/azure_cognitive_search/utils.py +++ b/python/semantic_kernel/connectors/memory/azure_cognitive_search/utils.py @@ -29,13 +29,12 @@ def get_search_index_async_client( ): """Return a client for Azure Cognitive Search. - Arguments: - search_endpoint {str} -- Optional endpoint (default: {None}). - admin_key {str} -- Optional API key (default: {None}). - azure_credential {AzureKeyCredential} -- Optional Azure credentials (default: {None}). - token_credential {TokenCredential} -- Optional Token credential (default: {None}). + Args: + search_endpoint (str): Optional endpoint (default: {None}). + admin_key (str): Optional API key (default: {None}). + azure_credential (AzureKeyCredential): Optional Azure credentials (default: {None}). + token_credential (TokenCredential): Optional Token credential (default: {None}). """ - ENV_VAR_ENDPOINT = "AZURE_COGNITIVE_SEARCH_ENDPOINT" ENV_VAR_API_KEY = "AZURE_COGNITIVE_SEARCH_ADMIN_KEY" @@ -83,13 +82,13 @@ def get_search_index_async_client( def get_index_schema(vector_size: int, vector_search_profile_name: str) -> list: """Return the schema of search indexes. - Arguments: - vector_size {int} -- The size of the vectors being stored in collection/index. + Args: + vector_size (int): The size of the vectors being stored in collection/index. + vector_search_profile_name (str): The name of the vector search profile. Returns: - list -- The Azure Cognitive Search schema as list type. + list: The Azure Cognitive Search schema as list type. """ - search_fields = [ SimpleField( name=SEARCH_FIELD_ID, @@ -149,13 +148,12 @@ def get_index_schema(vector_size: int, vector_search_profile_name: str) -> list: def get_field_selection(with_embeddings: bool) -> list[str]: """Get the list of fields to search and load. - Arguments: - with_embedding {bool} -- Whether to include the embedding vector field. + Args: + with_embeddings (bool): Whether to include the embedding vector field. Returns: - List[str] -- List of fields. + List[str]: List of fields. """ - field_selection = [ SEARCH_FIELD_ID, SEARCH_FIELD_TEXT, @@ -174,13 +172,13 @@ def get_field_selection(with_embeddings: bool) -> list[str]: def dict_to_memory_record(data: dict, with_embeddings: bool) -> MemoryRecord: """Converts a search result to a MemoryRecord. - Arguments: - data {dict} -- Azure Cognitive Search result data. + Args: + data (dict): Azure Cognitive Search result data. + with_embeddings (bool): Whether to include the embedding vector field. Returns: - MemoryRecord -- The MemoryRecord from Azure Cognitive Search Data Result. + MemoryRecord: The MemoryRecord from Azure Cognitive Search Data Result. """ - sk_result = MemoryRecord( id=decode_id(data[SEARCH_FIELD_ID]), key=data[SEARCH_FIELD_ID], @@ -196,15 +194,14 @@ def dict_to_memory_record(data: dict, with_embeddings: bool) -> MemoryRecord: def memory_record_to_search_record(record: MemoryRecord) -> dict: - """Convert a MemoryRecord to a dictionary + """Convert a MemoryRecord to a dictionary. - Arguments: - record {MemoryRecord} -- The MemoryRecord from Azure Cognitive Search Data Result. + Args: + record (MemoryRecord): The MemoryRecord from Azure Cognitive Search Data Result. Returns: - data {dict} -- Dictionary data. + data (dict): Dictionary data. """ - return { SEARCH_FIELD_ID: encode_id(record._id), SEARCH_FIELD_TEXT: str(record._text), @@ -222,7 +219,6 @@ def encode_id(id: str) -> str: Azure Cognitive Search keys can contain only letters, digits, underscore, dash, equal sign, recommending to encode values with a URL-safe algorithm. """ - id_bytes = id.encode("ascii") base64_bytes = base64.b64encode(id_bytes) return base64_bytes.decode("ascii") @@ -234,7 +230,6 @@ def decode_id(base64_id: str) -> str: Azure Cognitive Search keys can contain only letters, digits, underscore, dash, equal sign, recommending to encode values with a URL-safe algorithm. """ - base64_bytes = base64_id.encode("ascii") message_bytes = base64.b64decode(base64_bytes) return message_bytes.decode("ascii") diff --git a/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_memory_store.py b/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_memory_store.py index 8042f703492f..c8eea3d9b775 100644 --- a/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_memory_store.py +++ b/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_memory_store.py @@ -23,12 +23,14 @@ @experimental_class class AzureCosmosDBMemoryStore(MemoryStoreBase): - """A memory store that uses AzureCosmosDB for MongoDB vCore, to perform vector similarity search on a fully - managed MongoDB compatible database service. - https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/vcore/vector-search""" + """A memory store that uses AzureCosmosDB for MongoDB vCore. + + To perform vector similarity search on a fully managed MongoDB compatible database service. + https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/vcore/vector-search. + """ # Right now this only supports Mongo, but set up to support more later. - apiStore: AzureCosmosDBStoreApi = None + api_store: AzureCosmosDBStoreApi = None mongodb_client = None database = None index_name = None @@ -54,16 +56,15 @@ def __init__( ef_construction: int = 64, ef_search: int = 40, ): + """Initializes a new instance of the AzureCosmosDBMemoryStore class.""" if vector_dimensions <= 0: raise MemoryConnectorInitializationError("Vector dimensions must be a positive number.") - # if connection_string is None: - # raise ValueError("Connection String cannot be empty.") if database_name is None: raise MemoryConnectorInitializationError("Database Name cannot be empty.") if index_name is None: raise MemoryConnectorInitializationError("Index Name cannot be empty.") - self.cosmosStore = cosmosStore + self.cosmos_store = cosmosStore self.index_name = index_name self.num_lists = num_lists self.similarity = similarity @@ -89,11 +90,10 @@ async def create( ef_search, env_file_path: str | None = None, ) -> MemoryStoreBase: - """Creates the underlying data store based on the API definition""" + """Creates the underlying data store based on the API definition.""" # Right now this only supports Mongo, but set up to support more later. apiStore: AzureCosmosDBStoreApi = None if cosmos_api == "mongo-vcore": - cosmosdb_settings = None try: cosmosdb_settings = AzureCosmosDBSettings.create(env_file_path=env_file_path) @@ -141,117 +141,117 @@ async def create( async def create_collection(self, collection_name: str) -> None: """Creates a new collection in the data store. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. + Args: + collection_name (str): The name associated with a collection of embeddings. Returns: None """ - return await self.cosmosStore.create_collection(collection_name) + return await self.cosmos_store.create_collection(collection_name) async def get_collections(self) -> list[str]: """Gets the list of collections. Returns: - List[str] -- The list of collections. + List[str]: The list of collections. """ - return await self.cosmosStore.get_collections() + return await self.cosmos_store.get_collections() async def delete_collection(self, collection_name: str) -> None: """Deletes a collection. - Arguments: - collection_name {str} -- The name of the collection to delete. + Args: + collection_name (str): The name of the collection to delete. Returns: None """ - return await self.cosmosStore.delete_collection("") + return await self.cosmos_store.delete_collection("") async def does_collection_exist(self, collection_name: str) -> bool: """Checks if a collection exists. - Arguments: - collection_name {str} -- The name of the collection to check. + Args: + collection_name (str): The name of the collection to check. Returns: - bool -- True if the collection exists; otherwise, False. + bool: True if the collection exists; otherwise, False. """ - return await self.cosmosStore.does_collection_exist("") + return await self.cosmos_store.does_collection_exist("") async def upsert(self, collection_name: str, record: MemoryRecord) -> str: """Upsert a record. - Arguments: - collection_name {str} -- The name of the collection to upsert the record into. - record {MemoryRecord} -- The record to upsert. + Args: + collection_name (str): The name of the collection to upsert the record into. + record (MemoryRecord): The record to upsert. Returns: - str -- The unique record id of the record. + str: The unique record id of the record. """ - return await self.cosmosStore.upsert("", record) + return await self.cosmos_store.upsert("", record) async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: """Upsert a batch of records. - Arguments: - collection_name {str} -- The name of the collection to upsert the records into. - records {List[MemoryRecord]} -- The records to upsert. + Args: + collection_name (str): The name of the collection to upsert the records into. + records (List[MemoryRecord]): The records to upsert. Returns: - List[str] -- The unique database keys of the records. + List[str]: The unique database keys of the records. """ - return await self.cosmosStore.upsert_batch("", records) + return await self.cosmos_store.upsert_batch("", records) async def get(self, collection_name: str, key: str, with_embedding: bool) -> MemoryRecord: """Gets a record. - Arguments: - collection_name {str} -- The name of the collection to get the record from. - key {str} -- The unique database key of the record. - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the record from. + key (str): The unique database key of the record. + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - MemoryRecord -- The record. + MemoryRecord: The record. """ - return await self.cosmosStore.get("", key, with_embedding) + return await self.cosmos_store.get("", key, with_embedding) async def get_batch(self, collection_name: str, keys: list[str], with_embeddings: bool) -> list[MemoryRecord]: """Gets a batch of records. - Arguments: - collection_name {str} -- The name of the collection to get the records from. - keys {List[str]} -- The unique database keys of the records. - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + Args: + collection_name (str): The name of the collection to get the records from. + keys (List[str]): The unique database keys of the records. + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[MemoryRecord] -- The records. + List[MemoryRecord]: The records. """ - return await self.cosmosStore.get_batch("", keys, with_embeddings) + return await self.cosmos_store.get_batch("", keys, with_embeddings) async def remove(self, collection_name: str, key: str) -> None: """Removes a record. - Arguments: - collection_name {str} -- The name of the collection to remove the record from. - key {str} -- The unique database key of the record to remove. + Args: + collection_name (str): The name of the collection to remove the record from. + key (str): The unique database key of the record to remove. Returns: None """ - return await self.cosmosStore.remove("", key) + return await self.cosmos_store.remove("", key) async def remove_batch(self, collection_name: str, keys: list[str]) -> None: """Removes a batch of records. - Arguments: - collection_name {str} -- The name of the collection to remove the records from. - keys {List[str]} -- The unique database keys of the records to remove. + Args: + collection_name (str): The name of the collection to remove the records from. + keys (List[str]): The unique database keys of the records to remove. Returns: None """ - return await self.cosmosStore.remove_batch("", keys) + return await self.cosmos_store.remove_batch("", keys) async def get_nearest_matches( self, @@ -264,16 +264,16 @@ async def get_nearest_matches( """Gets the nearest matches to an embedding using vector configuration. Parameters: - collection_name (str) -- The name of the collection to get the nearest matches from. - embedding (ndarray) -- The embedding to find the nearest matches to. - limit {int} -- The maximum number of matches to return. - min_relevance_score {float} -- The minimum relevance score of the matches. (default: {0.0}) - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + collection_name (str) : The name of the collection to get the nearest matches from. + embedding (ndarray) : The embedding to find the nearest matches to. + limit (int): The maximum number of matches to return. + min_relevance_score (float): The minimum relevance score of the matches. (default: {0.0}) + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[Tuple[MemoryRecord, float]] -- The records and their relevance scores. + List[Tuple[MemoryRecord, float]]: The records and their relevance scores. """ - return await self.cosmosStore.get_nearest_matches("", embedding, limit, min_relevance_score, with_embeddings) + return await self.cosmos_store.get_nearest_matches("", embedding, limit, min_relevance_score, with_embeddings) async def get_nearest_match( self, @@ -284,13 +284,13 @@ async def get_nearest_match( ) -> tuple[MemoryRecord, float]: """Gets the nearest match to an embedding using vector configuration parameters. - Arguments: - collection_name {str} -- The name of the collection to get the nearest match from. - embedding {ndarray} -- The embedding to find the nearest match to. - min_relevance_score {float} -- The minimum relevance score of the match. (default: {0.0}) - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the nearest match from. + embedding (ndarray): The embedding to find the nearest match to. + min_relevance_score (float): The minimum relevance score of the match. (default: {0.0}) + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - Tuple[MemoryRecord, float] -- The record and the relevance score. + Tuple[MemoryRecord, float]: The record and the relevance score. """ - return await self.cosmosStore.get_nearest_match("", embedding, min_relevance_score, with_embedding) + return await self.cosmos_store.get_nearest_match("", embedding, min_relevance_score, with_embedding) diff --git a/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_store_api.py b/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_store_api.py index a3b31ec1bae3..fcacfdd5516e 100644 --- a/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_store_api.py +++ b/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_store_api.py @@ -14,42 +14,123 @@ class AzureCosmosDBStoreApi(ABC): @abstractmethod async def create_collection(self, collection_name: str) -> None: + """Creates a new collection in the data store. + + Args: + collection_name (str): The name associated with a collection of embeddings. + """ raise NotImplementedError @abstractmethod async def get_collections(self) -> list[str]: + """Gets all collection names in the data store. + + Returns: + List[str]: A group of collection names. + """ raise NotImplementedError @abstractmethod async def delete_collection(self, collection_name: str) -> None: + """Deletes a collection from the data store. + + Args: + collection_name (str): The name associated with a collection of embeddings. + """ raise NotImplementedError @abstractmethod async def does_collection_exist(self, collection_name: str) -> bool: + """Determines if a collection exists in the data store. + + Args: + collection_name (str): The name associated with a collection of embeddings. + + Returns: + bool: True if given collection exists, False if not. + """ raise NotImplementedError @abstractmethod async def upsert(self, collection_name: str, record: MemoryRecord) -> str: + """Upserts a memory record into the data store. + + Does not guarantee that the collection exists. + If the record already exists, it will be updated. + If the record does not exist, it will be created. + + Args: + collection_name (str): The name associated with a collection of embeddings. + record (MemoryRecord): The memory record to upsert. + + Returns: + str: The unique identifier for the memory record. + """ raise NotImplementedError @abstractmethod async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: + """Upserts a group of memory records into the data store. + + Does not guarantee that the collection exists. + If the record already exists, it will be updated. + If the record does not exist, it will be created. + + Args: + collection_name (str): The name associated with a collection of embeddings. + records (MemoryRecord): The memory records to upsert. + + Returns: + List[str]: The unique identifiers for the memory records. + """ raise NotImplementedError @abstractmethod async def get(self, collection_name: str, key: str, with_embedding: bool) -> MemoryRecord: + """Gets a memory record from the data store. Does not guarantee that the collection exists. + + Args: + collection_name (str): The name associated with a collection of embeddings. + key (str): The unique id associated with the memory record to get. + with_embedding (bool): If true, the embedding will be returned in the memory record. + + Returns: + MemoryRecord: The memory record if found + """ raise NotImplementedError @abstractmethod async def get_batch(self, collection_name: str, keys: list[str], with_embeddings: bool) -> list[MemoryRecord]: + """Gets a batch of memory records from the data store. Does not guarantee that the collection exists. + + Args: + collection_name (str): The name associated with a collection of embeddings. + keys (List[str]): The unique ids associated with the memory records to get. + with_embeddings (bool): If true, the embedding will be returned in the memory records. + + Returns: + List[MemoryRecord]: The memory records associated with the unique keys provided. + """ raise NotImplementedError @abstractmethod async def remove(self, collection_name: str, key: str) -> None: + """Removes a memory record from the data store. Does not guarantee that the collection exists. + + Args: + collection_name (str): The name associated with a collection of embeddings. + key (str): The unique id associated with the memory record to remove. + """ raise NotImplementedError @abstractmethod async def remove_batch(self, collection_name: str, keys: list[str]) -> None: + """Removes a batch of memory records from the data store. Does not guarantee that the collection exists. + + Args: + collection_name (str): The name associated with a collection of embeddings. + keys (List[str]): The unique ids associated with the memory records to remove. + """ raise NotImplementedError @abstractmethod @@ -61,6 +142,19 @@ async def get_nearest_matches( min_relevance_score: float, with_embeddings: bool, ) -> list[tuple[MemoryRecord, float]]: + """Gets the nearest matches to an embedding of type float. Does not guarantee that the collection exists. + + Args: + collection_name (str): The name associated with a collection of embeddings. + embedding (ndarray): The embedding to compare the collection's embeddings with. + limit (int): The maximum number of similarity results to return. + min_relevance_score (float): The minimum relevance threshold for returned results. + with_embeddings (bool): If true, the embeddings will be returned in the memory records. + + Returns: + List[Tuple[MemoryRecord, float]]: A list of tuples where item1 is a MemoryRecord and item2 + is its similarity score as a float. + """ raise NotImplementedError @abstractmethod @@ -71,4 +165,15 @@ async def get_nearest_match( min_relevance_score: float, with_embedding: bool, ) -> tuple[MemoryRecord, float]: + """Gets the nearest match to an embedding of type float. Does not guarantee that the collection exists. + + Args: + collection_name (str): The name associated with a collection of embeddings. + embedding (ndarray): The embedding to compare the collection's embeddings with. + min_relevance_score (float): The minimum relevance threshold for returned result. + with_embedding (bool): If true, the embeddings will be returned in the memory record. + + Returns: + Tuple[MemoryRecord, float]: A tuple consisting of the MemoryRecord and the similarity score as a float. + """ raise NotImplementedError diff --git a/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmosdb_settings.py b/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmosdb_settings.py index ea22d6e8276a..0ee064b2d0d8 100644 --- a/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmosdb_settings.py +++ b/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmosdb_settings.py @@ -8,9 +8,9 @@ @experimental_class class AzureCosmosDBSettings(BaseModelSettings): - """Azure CosmosDB model settings + """Azure CosmosDB model settings. - Optional: + Args: - connection_string: str - Azure CosmosDB connection string (Env var COSMOSDB_CONNECTION_STRING) """ @@ -19,4 +19,6 @@ class AzureCosmosDBSettings(BaseModelSettings): connection_string: SecretStr | None = None class Config(BaseModelSettings.Config): + """Pydantic configuration settings.""" + env_prefix = "COSMOSDB_" diff --git a/python/semantic_kernel/connectors/memory/azure_cosmosdb/cosmosdb_utils.py b/python/semantic_kernel/connectors/memory/azure_cosmosdb/cosmosdb_utils.py index bb6f77cb6ece..0e28647fd569 100644 --- a/python/semantic_kernel/connectors/memory/azure_cosmosdb/cosmosdb_utils.py +++ b/python/semantic_kernel/connectors/memory/azure_cosmosdb/cosmosdb_utils.py @@ -34,14 +34,13 @@ class CosmosDBVectorSearchType(str, Enum): @experimental_function def get_mongodb_search_client(connection_string: str, application_name: str): - """ - Returns a client for Azure Cosmos Mongo vCore Vector DB + """Returns a client for Azure Cosmos Mongo vCore Vector DB. - Arguments: - connection_string {str} + Args: + connection_string (str): The connection string for the Azure Cosmos Mongo vCore Vector DB. + application_name (str): The name of the application. """ - ENV_VAR_COSMOS_CONN_STR = "AZCOSMOS_CONNSTR" load_dotenv() diff --git a/python/semantic_kernel/connectors/memory/azure_cosmosdb/mongo_vcore_store_api.py b/python/semantic_kernel/connectors/memory/azure_cosmosdb/mongo_vcore_store_api.py index f3e57a637f68..8e2db4ba8209 100644 --- a/python/semantic_kernel/connectors/memory/azure_cosmosdb/mongo_vcore_store_api.py +++ b/python/semantic_kernel/connectors/memory/azure_cosmosdb/mongo_vcore_store_api.py @@ -1,13 +1,17 @@ # Copyright (c) Microsoft. All rights reserved. import json +import sys from typing import Any import numpy as np -from semantic_kernel.connectors.memory.azure_cosmosdb.azure_cosmos_db_store_api import ( - AzureCosmosDBStoreApi, -) +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + +from semantic_kernel.connectors.memory.azure_cosmosdb.azure_cosmos_db_store_api import AzureCosmosDBStoreApi from semantic_kernel.connectors.memory.azure_cosmosdb.cosmosdb_utils import ( CosmosDBSimilarityType, CosmosDBVectorSearchType, @@ -81,6 +85,7 @@ def __init__( ef_search: int, database=None, ): + """Initializes a new instance of the MongoStoreApi class.""" self.database = database self.collection_name = collection_name self.index_name = index_name @@ -92,6 +97,7 @@ def __init__( self.ef_construction = ef_construction self.ef_search = ef_search + @override async def create_collection(self, collection_name: str) -> None: if not await self.does_collection_exist(collection_name): if self.index_name not in self.database[collection_name].list_indexes(): @@ -156,19 +162,24 @@ def _get_vector_index_hnsw( } return command + @override async def get_collections(self) -> list[str]: return self.database.list_collection_names() + @override async def delete_collection(self, collection_name: str) -> None: return self.collection.drop() + @override async def does_collection_exist(self, collection_name: str) -> bool: return collection_name in self.database.list_collection_names() + @override async def upsert(self, collection_name: str, record: MemoryRecord) -> str: result = await self.upsert_batch(collection_name, [record]) return result[0] + @override async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: doc_ids: list[str] = [] cosmosRecords: list[dict] = [] @@ -188,6 +199,7 @@ async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) self.collection.insert_many(cosmosRecords) return doc_ids + @override async def get(self, collection_name: str, key: str, with_embedding: bool) -> MemoryRecord: if not with_embedding: result = self.collection.find_one({"_id": key}, {"embedding": 0}) @@ -202,6 +214,7 @@ async def get(self, collection_name: str, key: str, with_embedding: bool) -> Mem timestamp=result.get("timestamp", None), ) + @override async def get_batch(self, collection_name: str, keys: list[str], with_embeddings: bool) -> list[MemoryRecord]: if not with_embeddings: results = self.collection.find({"_id": {"$in": keys}}, {"embedding": 0}) @@ -220,12 +233,15 @@ async def get_batch(self, collection_name: str, keys: list[str], with_embeddings for result in results ] + @override async def remove(self, collection_name: str, key: str) -> None: self.collection.delete_one({"_id": key}) + @override async def remove_batch(self, collection_name: str, keys: list[str]) -> None: self.collection.delete_many({"_id": {"$in": keys}}) + @override async def get_nearest_matches( self, collection_name: str, @@ -303,6 +319,7 @@ def _get_pipeline_vector_hnsw( ] return pipeline + @override async def get_nearest_match( self, collection_name: str, diff --git a/python/semantic_kernel/connectors/memory/azure_cosmosdb_no_sql/azure_cosmosdb_no_sql_memory_store.py b/python/semantic_kernel/connectors/memory/azure_cosmosdb_no_sql/azure_cosmosdb_no_sql_memory_store.py index 5f653feb5411..c915fe74f0c6 100644 --- a/python/semantic_kernel/connectors/memory/azure_cosmosdb_no_sql/azure_cosmosdb_no_sql_memory_store.py +++ b/python/semantic_kernel/connectors/memory/azure_cosmosdb_no_sql/azure_cosmosdb_no_sql_memory_store.py @@ -1,8 +1,14 @@ # Copyright (c) Microsoft. All rights reserved. import json +import sys from typing import Any +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + import numpy as np from azure.cosmos.aio import ContainerProxy, CosmosClient, DatabaseProxy from numpy import ndarray @@ -12,10 +18,10 @@ from semantic_kernel.utils.experimental_decorator import experimental_class -# You can read more about vector search using AzureCosmosDBNoSQL here. -# https://aka.ms/CosmosVectorSearch @experimental_class class AzureCosmosDBNoSQLMemoryStore(MemoryStoreBase): + """You can read more about vector search using AzureCosmosDBNoSQL here: https://aka.ms/CosmosVectorSearch.""" + cosmos_client: CosmosClient = None database: DatabaseProxy container: ContainerProxy @@ -34,6 +40,7 @@ def __init__( indexing_policy: dict[str, Any] | None = None, cosmos_container_properties: dict[str, Any] | None = None, ): + """Initializes a new instance of the AzureCosmosDBNoSQLMemoryStore class.""" if indexing_policy["vectorIndexes"] is None or len(indexing_policy["vectorIndexes"]) == 0: raise ValueError("vectorIndexes cannot be null or empty in the indexing_policy.") if vector_embedding_policy is None or len(vector_embedding_policy["vectorEmbeddings"]) == 0: @@ -46,6 +53,7 @@ def __init__( self.indexing_policy = indexing_policy self.cosmos_container_properties = cosmos_container_properties + @override async def create_collection(self, collection_name: str) -> None: # Create the database if it already doesn't exist self.database = await self.cosmos_client.create_database_if_not_exists(id=self.database_name) @@ -58,19 +66,24 @@ async def create_collection(self, collection_name: str) -> None: vector_embedding_policy=self.vector_embedding_policy, ) + @override async def get_collections(self) -> list[str]: return [container["id"] async for container in self.database.list_containers()] + @override async def delete_collection(self, collection_name: str) -> None: return await self.database.delete_container(collection_name) + @override async def does_collection_exist(self, collection_name: str) -> bool: return collection_name in [container["id"] async for container in self.database.list_containers()] + @override async def upsert(self, collection_name: str, record: MemoryRecord) -> str: result = await self.upsert_batch(collection_name, [record]) return result[0] + @override async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: doc_ids: list[str] = [] for record in records: @@ -88,6 +101,7 @@ async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) doc_ids.append(cosmosRecord["id"]) return doc_ids + @override async def get(self, collection_name: str, key: str, with_embedding: bool) -> MemoryRecord: item = await self.container.read_item(key, partition_key=key) return MemoryRecord.local_record( @@ -99,6 +113,7 @@ async def get(self, collection_name: str, key: str, with_embedding: bool) -> Mem timestamp=item.get("timestamp", None), ) + @override async def get_batch(self, collection_name: str, keys: list[str], with_embeddings: bool) -> list[MemoryRecord]: query = "SELECT * FROM c WHERE ARRAY_CONTAINS(@ids, c.id)" parameters = [{"name": "@ids", "value": keys}] @@ -117,23 +132,24 @@ async def get_batch(self, collection_name: str, keys: list[str], with_embeddings all_results.append(item) return all_results + @override async def remove(self, collection_name: str, key: str) -> None: await self.container.delete_item(key, partition_key=key) + @override async def remove_batch(self, collection_name: str, keys: list[str]) -> None: for key in keys: await self.container.delete_item(key, partition_key=key) + @override async def get_nearest_matches( self, collection_name: str, embedding: ndarray, limit: int, min_relevance_score: float, with_embeddings: bool ) -> list[tuple[MemoryRecord, float]]: embedding_key = self.vector_embedding_policy["vectorEmbeddings"][0]["path"][1:] query = ( - "SELECT TOP {} c.id, c.{}, c.text, c.description, c.metadata, " - "c.timestamp, VectorDistance(c.{}, {}) AS SimilarityScore FROM c ORDER BY " - "VectorDistance(c.{}, {})".format( - limit, embedding_key, embedding_key, embedding.tolist(), embedding_key, embedding.tolist() - ) + f"SELECT TOP {limit} c.id, c.{embedding_key}, c.text, c.description, c.metadata, " # nosec + f"c.timestamp, VectorDistance(c.{embedding_key}, {embedding.tolist()}) AS SimilarityScore FROM c ORDER BY " # nosec + f"VectorDistance(c.{embedding_key}, {embedding.tolist()})" # nosec ) items = [item async for item in self.container.query_items(query=query)] @@ -153,6 +169,7 @@ async def get_nearest_matches( nearest_results.append((result, score)) return nearest_results + @override async def get_nearest_match( self, collection_name: str, embedding: ndarray, min_relevance_score: float, with_embedding: bool ) -> tuple[MemoryRecord, float]: diff --git a/python/semantic_kernel/connectors/memory/chroma/chroma_memory_store.py b/python/semantic_kernel/connectors/memory/chroma/chroma_memory_store.py index c26fde26d3aa..10ee5c5580d8 100644 --- a/python/semantic_kernel/connectors/memory/chroma/chroma_memory_store.py +++ b/python/semantic_kernel/connectors/memory/chroma/chroma_memory_store.py @@ -1,10 +1,16 @@ # Copyright (c) Microsoft. All rights reserved. import logging -from typing import TYPE_CHECKING, Optional +import sys +from typing import TYPE_CHECKING, Any, Optional from numpy import array, ndarray +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + from semantic_kernel.connectors.memory.chroma.utils import chroma_compute_similarity_scores, query_results_to_records from semantic_kernel.exceptions import ServiceInitializationError, ServiceResourceNotFoundError from semantic_kernel.memory.memory_record import MemoryRecord @@ -27,10 +33,10 @@ def __init__( self, persist_directory: str | None = None, client_settings: Optional["chromadb.config.Settings"] = None, - **kwargs, + **kwargs: Any, ) -> None: - """ - ChromaMemoryStore provides an interface to store and retrieve data using ChromaDB. + """ChromaMemoryStore provides an interface to store and retrieve data using ChromaDB. + Collection names with uppercase characters are not supported by ChromaDB, they will be automatically converted. Args: @@ -39,6 +45,8 @@ def __init__( client_settings (Optional["chromadb.config.Settings"], optional): A Settings instance to configure the ChromaDB client. Defaults to None, which means the default settings for ChromaDB will be used. similarity_fetch_limit (int, optional): The maximum number of results to calculate cosine-similarity. + **kwargs: Additional keyword arguments. + Example: # Create a ChromaMemoryStore with a local specified directory for data persistence chroma_local_data_store = ChromaMemoryStore(persist_directory='/path/to/persist/directory') @@ -74,11 +82,12 @@ def __init__( async def create_collection(self, collection_name: str) -> None: """Creates a new collection in Chroma if it does not exist. - To prevent downloading model file from embedding_function, - embedding_function is set to "DoNotUseChromaEmbeddingFunction". - Arguments: - collection_name {str} -- The name of the collection to create. + To prevent downloading model file from embedding_function, + embedding_function is set to "DoNotUseChromaEmbeddingFunction". + + Args: + collection_name (str): The name of the collection to create. The name of the collection will be converted to snake case. Returns: @@ -86,6 +95,7 @@ async def create_collection(self, collection_name: str) -> None: """ self._client.create_collection(name=collection_name) + @override async def get_collection(self, collection_name: str) -> Optional["Collection"]: try: # Current version of ChromeDB rejects camel case collection names. @@ -97,15 +107,15 @@ async def get_collections(self) -> list[str]: """Gets the list of collections. Returns: - List[str] -- The list of collections. + List[str]: The list of collections. """ return [collection.name for collection in self._client.list_collections()] async def delete_collection(self, collection_name: str) -> None: """Deletes a collection. - Arguments: - collection_name {str} -- The name of the collection to delete. + Args: + collection_name (str): The name of the collection to delete. Returns: None @@ -115,11 +125,11 @@ async def delete_collection(self, collection_name: str) -> None: async def does_collection_exist(self, collection_name: str) -> bool: """Checks if a collection exists. - Arguments: - collection_name {str} -- The name of the collection to check. + Args: + collection_name (str): The name of the collection to check. Returns: - bool -- True if the collection exists; otherwise, False. + bool: True if the collection exists; otherwise, False. """ if await self.get_collection(collection_name) is None: return False @@ -127,14 +137,14 @@ async def does_collection_exist(self, collection_name: str) -> bool: return True async def upsert(self, collection_name: str, record: MemoryRecord) -> str: - """Upserts a single MemoryRecord. + """Upsert a single MemoryRecord. - Arguments: - collection_name {str} -- The name of the collection to upsert the record into. - records {MemoryRecord} -- The record to upsert. + Args: + collection_name (str): The name of the collection to upsert the record into. + record (MemoryRecord): The record to upsert. Returns: - List[str] -- The unique database key of the record. + List[str]: The unique database key of the record. """ collection = await self.get_collection(collection_name) if collection is None: @@ -160,14 +170,14 @@ async def upsert(self, collection_name: str, record: MemoryRecord) -> str: return record._key async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: - """Upserts a batch of records. + """Upsert a batch of records. - Arguments: - collection_name {str} -- The name of the collection to upsert the records into. - records {List[MemoryRecord]} -- The records to upsert. + Args: + collection_name (str): The name of the collection to upsert the records into. + records (List[MemoryRecord]): The records to upsert. Returns: - List[str] -- The unique database keys of the records. In Pinecone, these are the record IDs. + List[str]: The unique database keys of the records. In Pinecone, these are the record IDs. """ # upsert is checking collection existence return [await self.upsert(collection_name, record) for record in records] @@ -175,13 +185,13 @@ async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) async def get(self, collection_name: str, key: str, with_embedding: bool) -> MemoryRecord: """Gets a record. - Arguments: - collection_name {str} -- The name of the collection to get the record from. - key {str} -- The unique database key of the record. - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the record from. + key (str): The unique database key of the record. + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - MemoryRecord -- The record. + MemoryRecord: The record. """ records = await self.get_batch(collection_name, [key], with_embedding) try: @@ -194,13 +204,13 @@ async def get(self, collection_name: str, key: str, with_embedding: bool) -> Mem async def get_batch(self, collection_name: str, keys: list[str], with_embeddings: bool) -> list[MemoryRecord]: """Gets a batch of records. - Arguments: - collection_name {str} -- The name of the collection to get the records from. - keys {List[str]} -- The unique database keys of the records. - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + Args: + collection_name (str): The name of the collection to get the records from. + keys (List[str]): The unique database keys of the records. + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[MemoryRecord] -- The records. + List[MemoryRecord]: The records. """ collection = await self.get_collection(collection_name) if collection is None: @@ -215,9 +225,9 @@ async def get_batch(self, collection_name: str, keys: list[str], with_embeddings async def remove(self, collection_name: str, key: str) -> None: """Removes a record. - Arguments: - collection_name {str} -- The name of the collection to remove the record from. - key {str} -- The unique database key of the record to remove. + Args: + collection_name (str): The name of the collection to remove the record from. + key (str): The unique database key of the record to remove. Returns: None @@ -227,9 +237,9 @@ async def remove(self, collection_name: str, key: str) -> None: async def remove_batch(self, collection_name: str, keys: list[str]) -> None: """Removes a batch of records. - Arguments: - collection_name {str} -- The name of the collection to remove the records from. - keys {List[str]} -- The unique database keys of the records to remove. + Args: + collection_name (str): The name of the collection to remove the records from. + keys (List[str]): The unique database keys of the records to remove. Returns: None @@ -248,15 +258,15 @@ async def get_nearest_matches( ) -> list[tuple[MemoryRecord, float]]: """Gets the nearest matches to an embedding using cosine similarity. - Arguments: - collection_name {str} -- The name of the collection to get the nearest matches from. - embedding {ndarray} -- The embedding to find the nearest matches to. - limit {int} -- The maximum number of matches to return. - min_relevance_score {float} -- The minimum relevance score of the matches. (default: {0.0}) - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + Args: + collection_name (str): The name of the collection to get the nearest matches from. + embedding (ndarray): The embedding to find the nearest matches to. + limit (int): The maximum number of matches to return. + min_relevance_score (float): The minimum relevance score of the matches. (default: {0.0}) + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[Tuple[MemoryRecord, float]] -- The records and their relevance scores. + List[Tuple[MemoryRecord, float]]: The records and their relevance scores. """ if with_embeddings is False: logger.warning( @@ -315,14 +325,14 @@ async def get_nearest_match( ) -> tuple[MemoryRecord, float]: """Gets the nearest match to an embedding using cosine similarity. - Arguments: - collection_name {str} -- The name of the collection to get the nearest match from. - embedding {ndarray} -- The embedding to find the nearest match to. - min_relevance_score {float} -- The minimum relevance score of the match. (default: {0.0}) - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the nearest match from. + embedding (ndarray): The embedding to find the nearest match to. + min_relevance_score (float): The minimum relevance score of the match. (default: {0.0}) + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - Tuple[MemoryRecord, float] -- The record and the relevance score. + Tuple[MemoryRecord, float]: The record and the relevance score. """ results = await self.get_nearest_matches( collection_name=collection_name, @@ -332,38 +342,3 @@ async def get_nearest_match( with_embeddings=with_embedding, ) return results[0] - - -if __name__ == "__main__": - import asyncio - - import numpy as np - - memory = ChromaMemoryStore() - memory_record1 = MemoryRecord( - id="test_id1", - text="sample text1", - is_reference=False, - embedding=np.array([0.5, 0.5]), - description="description", - external_source_name="external source", - timestamp="timestamp", - ) - memory_record2 = MemoryRecord( - id="test_id2", - text="sample text2", - is_reference=False, - embedding=np.array([0.25, 0.75]), - description="description", - external_source_name="external source", - timestamp="timestamp", - ) - - asyncio.run(memory.create_collection("test_collection")) - collection = asyncio.run(memory.get_collection("test_collection")) - - asyncio.run(memory.upsert_batch(collection.name, [memory_record1, memory_record2])) - - result = asyncio.run(memory.get(collection.name, "test_id1", True)) - results = asyncio.run(memory.get_nearest_match("test_collection", np.array([0.5, 0.5]))) - print(results) diff --git a/python/semantic_kernel/connectors/memory/chroma/utils.py b/python/semantic_kernel/connectors/memory/chroma/utils.py index 347f3b2f1cb0..d056a7602610 100644 --- a/python/semantic_kernel/connectors/memory/chroma/utils.py +++ b/python/semantic_kernel/connectors/memory/chroma/utils.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from numpy import array, linalg, ndarray @@ -14,6 +14,7 @@ def camel_to_snake(camel_str): + """Convert camel case to snake case.""" snake_str = "" for i, char in enumerate(camel_str): if char.isupper(): @@ -26,10 +27,13 @@ def camel_to_snake(camel_str): def query_results_to_records(results: "QueryResult", with_embedding: bool) -> list[MemoryRecord]: - # if results has only one record, it will be a list instead of a nested list - # this is to make sure that results is always a nested list - # {'ids': ['test_id1'], 'embeddings': [[...]], 'documents': ['sample text1'], 'metadatas': [{...}]} - # => {'ids': [['test_id1']], 'embeddings': [[[...]]], 'documents': [['sample text1']], 'metadatas': [[{...}]]} + """Turn query results into Memory Records. + + If results has only one record, it will be a list instead of a nested list + this is to make sure that results is always a nested list + {'ids': ['test_id1'], 'embeddings': [[...]], 'documents': ['sample text1'], 'metadatas': [{...}]} + => {'ids': [['test_id1']], 'embeddings': [[[...]]], 'documents': [['sample text1']], 'metadatas': [[{...}]]} + """ try: if isinstance(results["ids"][0], str): for k, v in results.items(): @@ -83,16 +87,17 @@ def query_results_to_records(results: "QueryResult", with_embedding: bool) -> li return memory_records -def chroma_compute_similarity_scores(embedding: ndarray, embedding_array: ndarray, **kwargs) -> ndarray: +def chroma_compute_similarity_scores(embedding: ndarray, embedding_array: ndarray, **kwargs: Any) -> ndarray: """Computes the cosine similarity scores between a query embedding and a group of embeddings. - Arguments: - embedding {ndarray} -- The query embedding. - embedding_array {ndarray} -- The group of embeddings. + + Args: + embedding (ndarray): The query embedding. + embedding_array (ndarray): The group of embeddings. + **kwargs: Additional keyword arguments. + Returns: - ndarray -- The cosine similarity scores. + ndarray: The cosine similarity scores. """ - if kwargs.get("logger"): - logger.warning("The `logger` parameter is deprecated. Please use the `logging` module instead.") query_norm = linalg.norm(embedding) collection_norm = linalg.norm(embedding_array, axis=1) diff --git a/python/semantic_kernel/connectors/memory/memory_settings_base.py b/python/semantic_kernel/connectors/memory/memory_settings_base.py index 79366ba2e528..084f82cd78ed 100644 --- a/python/semantic_kernel/connectors/memory/memory_settings_base.py +++ b/python/semantic_kernel/connectors/memory/memory_settings_base.py @@ -10,6 +10,8 @@ class BaseModelSettings(BaseSettings): env_file_path: str | None = None class Config: + """Pydantic configuration settings.""" + env_file = None env_file_encoding = "utf-8" extra = "ignore" @@ -17,6 +19,7 @@ class Config: @classmethod def create(cls, **kwargs): + """Create an instance of the class.""" if "env_file_path" in kwargs and kwargs["env_file_path"]: cls.Config.env_file = kwargs["env_file_path"] else: diff --git a/python/semantic_kernel/connectors/memory/milvus/milvus_memory_store.py b/python/semantic_kernel/connectors/memory/milvus/milvus_memory_store.py index aa224ec79741..1ee2c1891e84 100644 --- a/python/semantic_kernel/connectors/memory/milvus/milvus_memory_store.py +++ b/python/semantic_kernel/connectors/memory/milvus/milvus_memory_store.py @@ -51,6 +51,7 @@ @experimental_function def memoryrecord_to_milvus_dict(mem: MemoryRecord) -> dict[str, Any]: """Convert a memoryrecord into a dict. + Args: mem (MemoryRecord): MemoryRecord to convert. @@ -97,6 +98,7 @@ def milvus_dict_to_memoryrecord(milvus_dict: dict[str, Any]) -> MemoryRecord: @experimental_function def create_fields(dimensions: int) -> list[FieldSchema]: + """Create the fields for the Milvus collection.""" return [ FieldSchema( name=SEARCH_FIELD_ID, @@ -148,13 +150,13 @@ def __init__( self, uri: str = "http://localhost:19530", token: str | None = None, - **kwargs, + **kwargs: Any, ) -> None: - """MilvusMemoryStore allows for searching for records using Milvus/Zilliz Cloud. + """Memory store based on Milvus. For more details on how to get the service started, take a look here: - Milvus: https://milvus.io/docs/get_started.md - Zilliz Cloud: https://docs.zilliz.com/docs/quick-start + - Milvus: https://milvus.io/docs/get_started.md + - Zilliz Cloud: https://docs.zilliz.com/docs/quick-start Args: @@ -162,6 +164,7 @@ def __init__( "http://localhost:19530". token (Optional[str], optional): The token to connect to the cluster if authentication is required. Defaults to None. + **kwargs (Any): Unused. """ connections.connect("default", uri=uri, token=token) self.collections: dict[str, Collection] = {} @@ -259,7 +262,7 @@ async def upsert(self, collection_name: str, record: MemoryRecord) -> str: return res[0] async def upsert_batch(self, collection_name: str, records: list[MemoryRecord], batch_size=100) -> list[str]: - """_summary_ + """_summary_. Args: collection_name (str): The collection name. @@ -303,7 +306,7 @@ async def get(self, collection_name: str, key: str, with_embedding: bool) -> Mem return res[0] async def get_batch(self, collection_name: str, keys: list[str], with_embeddings: bool) -> list[MemoryRecord]: - """Get the MemoryRecords corresponding to the keys + """Get the MemoryRecords corresponding to the keys. Args: collection_name (str): _description_ @@ -429,7 +432,7 @@ async def get_nearest_match( embedding: ndarray, min_relevance_score: float = 0.0, with_embedding: bool = False, - ) -> tuple[MemoryRecord, float]: + ) -> tuple[MemoryRecord, float] | None: """Find the nearest match for an embedding. Args: diff --git a/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py b/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py index ced2094e2ad9..289d98e0f544 100644 --- a/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py +++ b/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py @@ -30,7 +30,7 @@ @experimental_class class MongoDBAtlasMemoryStore(MemoryStoreBase): - """Memory Store for MongoDB Atlas Vector Search Connections""" + """Memory Store for MongoDB Atlas Vector Search Connections.""" __slots__ = ("_mongo_client", "__database_name") @@ -46,6 +46,7 @@ def __init__( read_preference: ReadPreference | None = ReadPreference.PRIMARY, env_file_path: str | None = None, ): + """Initializes a new instance of the MongoDBAtlasMemoryStore class.""" from semantic_kernel.connectors.memory.mongodb_atlas import MongoDBAtlasSettings mongodb_settings = None @@ -70,22 +71,26 @@ def __init__( @property def database_name(self) -> str: + """The name of the database.""" return self.__database_name @property def database(self) -> core.AgnosticDatabase: + """The database object.""" return self._mongo_client[self.database_name] @property def index_name(self) -> str: + """The name of the index.""" return self.__index_name @property def num_candidates(self) -> int: + """The number of candidates to return.""" return self.__num_candidates async def close(self): - """Async close connection, invoked by MemoryStoreBase.__aexit__()""" + """Async close connection, invoked by MemoryStoreBase.__aexit__().""" if self._mongo_client: self._mongo_client.close() self._mongo_client = None @@ -93,8 +98,8 @@ async def close(self): async def create_collection(self, collection_name: str) -> None: """Creates a new collection in the data store. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. + Args: + collection_name (str): The name associated with a collection of embeddings. Returns: None @@ -108,15 +113,15 @@ async def get_collections( """Gets all collection names in the data store. Returns: - List[str] -- A group of collection names. + List[str]: A group of collection names. """ return await self.database.list_collection_names() async def delete_collection(self, collection_name: str) -> None: """Deletes a collection from the data store. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. + Args: + collection_name (str): The name associated with a collection of embeddings. Returns: None @@ -126,49 +131,52 @@ async def delete_collection(self, collection_name: str) -> None: async def does_collection_exist(self, collection_name: str) -> bool: """Determines if a collection exists in the data store. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. + Args: + collection_name (str): The name associated with a collection of embeddings. Returns: - bool -- True if given collection exists, False if not. + bool: True if given collection exists, False if not. """ return collection_name in (await self.get_collections()) async def upsert(self, collection_name: str, record: MemoryRecord) -> str: - """Upserts a memory record into the data store. Does not guarantee that the collection exists. + """Upserts a memory record into the data store. + + Does not guarantee that the collection exists. If the record already exists, it will be updated. If the record does not exist, it will be created. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - record {MemoryRecord} -- The memory record to upsert. + Args: + collection_name (str): The name associated with a collection of embeddings. + record (MemoryRecord): The memory record to upsert. Returns: - str -- The unique identifier for the memory record. + str: The unique identifier for the memory record. """ - document: Mapping[str, Any] = memory_record_to_mongo_document(record) update_result: results.UpdateResult = await self.database[collection_name].update_one( document, {"$set": document}, upsert=True ) - assert update_result.acknowledged + if not update_result.acknowledged: + raise ValueError("Upsert failed") return record._id async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: - """Upserts a group of memory records into the data store. Does not guarantee that the collection exists. + """Upserts a group of memory records into the data store. + + Does not guarantee that the collection exists. If the record already exists, it will be updated. If the record does not exist, it will be created. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - records {MemoryRecord} -- The memory records to upsert. + Args: + collection_name (str): The name associated with a collection of embeddings. + records (MemoryRecord): The memory records to upsert. Returns: - List[str] -- The unique identifiers for the memory records. + List[str]: The unique identifiers for the memory records. """ - upserts: list[UpdateOne] = [] for record in records: document = memory_record_to_mongo_document(record) @@ -183,19 +191,20 @@ async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) bulk_update_result.matched_count, bulk_update_result.upserted_count, ) - assert bulk_update_result.matched_count + bulk_update_result.upserted_count == len(records) + if bulk_update_result.matched_count + bulk_update_result.upserted_count != len(records): + raise ValueError("Batch upsert failed") return [record._id for record in records] async def get(self, collection_name: str, key: str, with_embedding: bool) -> MemoryRecord: """Gets a memory record from the data store. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - key {str} -- The unique id associated with the memory record to get. - with_embedding {bool} -- If true, the embedding will be returned in the memory record. + Args: + collection_name (str): The name associated with a collection of embeddings. + key (str): The unique id associated with the memory record to get. + with_embedding (bool): If true, the embedding will be returned in the memory record. Returns: - MemoryRecord -- The memory record if found + MemoryRecord: The memory record if found """ document = await self.database[collection_name].find_one({MONGODB_FIELD_ID: key}) @@ -204,13 +213,13 @@ async def get(self, collection_name: str, key: str, with_embedding: bool) -> Mem async def get_batch(self, collection_name: str, keys: list[str], with_embeddings: bool) -> list[MemoryRecord]: """Gets a batch of memory records from the data store. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - keys {List[str]} -- The unique ids associated with the memory records to get. - with_embeddings {bool} -- If true, the embedding will be returned in the memory records. + Args: + collection_name (str): The name associated with a collection of embeddings. + keys (List[str]): The unique ids associated with the memory records to get. + with_embeddings (bool): If true, the embedding will be returned in the memory records. Returns: - List[MemoryRecord] -- The memory records associated with the unique keys provided. + List[MemoryRecord]: The memory records associated with the unique keys provided. """ results = self.database[collection_name].find({MONGODB_FIELD_ID: {"$in": keys}}) @@ -221,9 +230,9 @@ async def get_batch(self, collection_name: str, keys: list[str], with_embeddings async def remove(self, collection_name: str, key: str) -> None: """Removes a memory record from the data store. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - key {str} -- The unique id associated with the memory record to remove. + Args: + collection_name (str): The name associated with a collection of embeddings. + key (str): The unique id associated with the memory record to remove. Returns: None @@ -235,9 +244,9 @@ async def remove(self, collection_name: str, key: str) -> None: async def remove_batch(self, collection_name: str, keys: list[str]) -> None: """Removes a batch of memory records from the data store. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - keys {List[str]} -- The unique ids associated with the memory records to remove. + Args: + collection_name (str): The name associated with a collection of embeddings. + keys (List[str]): The unique ids associated with the memory records to remove. Returns: None @@ -258,14 +267,15 @@ async def get_nearest_matches( ) -> list[tuple[MemoryRecord, float]]: """Gets the nearest matches to an embedding of type float. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - embedding {ndarray} -- The embedding to compare the collection's embeddings with. - limit {int} -- The maximum number of similarity results to return, defaults to 1. - min_relevance_score {float} -- The minimum relevance threshold for returned results. - with_embeddings {bool} -- If true, the embeddings will be returned in the memory records. + Args: + collection_name (str): The name associated with a collection of embeddings. + embedding (ndarray): The embedding to compare the collection's embeddings with. + limit (int): The maximum number of similarity results to return, defaults to 1. + min_relevance_score (float): The minimum relevance threshold for returned results. + with_embeddings (bool): If true, the embeddings will be returned in the memory records. + Returns: - List[Tuple[MemoryRecord, float]] -- A list of tuples where item1 is a MemoryRecord and item2 + List[Tuple[MemoryRecord, float]]: A list of tuples where item1 is a MemoryRecord and item2 is its similarity score as a float. """ pipeline: list[dict[str, Any]] = [] @@ -305,14 +315,14 @@ async def get_nearest_match( ) -> tuple[MemoryRecord, float]: """Gets the nearest match to an embedding of type float. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - embedding {ndarray} -- The embedding to compare the collection's embeddings with. - min_relevance_score {float} -- The minimum relevance threshold for returned result. - with_embedding {bool} -- If true, the embeddings will be returned in the memory record. + Args: + collection_name (str): The name associated with a collection of embeddings. + embedding (ndarray): The embedding to compare the collection's embeddings with. + min_relevance_score (float): The minimum relevance threshold for returned result. + with_embedding (bool): If true, the embeddings will be returned in the memory record. Returns: - Tuple[MemoryRecord, float] -- A tuple consisting of the MemoryRecord and the similarity score as a float. + Tuple[MemoryRecord, float]: A tuple consisting of the MemoryRecord and the similarity score as a float. """ matches: list[tuple[MemoryRecord, float]] = await self.get_nearest_matches( collection_name=collection_name, diff --git a/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_settings.py b/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_settings.py index 959925dece33..9f1dda5bcb74 100644 --- a/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_settings.py +++ b/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_settings.py @@ -8,9 +8,9 @@ @experimental_class class MongoDBAtlasSettings(BaseModelSettings): - """MongoDB Atlas model settings + """MongoDB Atlas model settings. - Optional: + Args: - connection_string: str - MongoDB Atlas connection string (Env var MONGODB_ATLAS_CONNECTION_STRING) """ @@ -18,4 +18,6 @@ class MongoDBAtlasSettings(BaseModelSettings): connection_string: SecretStr | None = None class Config(BaseModelSettings.Config): + """Pydantic configuration settings.""" + env_prefix = "MONGODB_ATLAS_" diff --git a/python/semantic_kernel/connectors/memory/mongodb_atlas/utils.py b/python/semantic_kernel/connectors/memory/mongodb_atlas/utils.py index 07129bc2a44f..cb415f45377c 100644 --- a/python/semantic_kernel/connectors/memory/mongodb_atlas/utils.py +++ b/python/semantic_kernel/connectors/memory/mongodb_atlas/utils.py @@ -22,11 +22,12 @@ def document_to_memory_record(data: dict, with_embeddings: bool) -> MemoryRecord: """Converts a search result to a MemoryRecord. - Arguments: - data {dict} -- Azure Cognitive Search result data. + Args: + data (dict): Azure Cognitive Search result data. + with_embeddings (bool): Whether to include embeddings. Returns: - MemoryRecord -- The MemoryRecord from Azure Cognitive Search Data Result. + MemoryRecord: The MemoryRecord from Azure Cognitive Search Data Result. """ meta = data.get(MONGODB_FIELD_METADATA, {}) @@ -44,15 +45,14 @@ def document_to_memory_record(data: dict, with_embeddings: bool) -> MemoryRecord def memory_record_to_mongo_document(record: MemoryRecord) -> dict: - """Convert a MemoryRecord to a dictionary + """Convert a MemoryRecord to a dictionary. - Arguments: - record {MemoryRecord} -- The MemoryRecord from Azure Cognitive Search Data Result. + Args: + record (MemoryRecord): The MemoryRecord from Azure Cognitive Search Data Result. Returns: - data {dict} -- Dictionary data. + data (dict): Dictionary data. """ - return { MONGODB_FIELD_ID: record._id, MONGODB_FIELD_METADATA: { diff --git a/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py b/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py index f6abaa266a5d..815bbdd4e1c6 100644 --- a/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py +++ b/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py @@ -8,10 +8,7 @@ from pydantic import ValidationError from semantic_kernel.connectors.memory.pinecone.pinecone_settings import PineconeSettings -from semantic_kernel.connectors.memory.pinecone.utils import ( - build_payload, - parse_payload, -) +from semantic_kernel.connectors.memory.pinecone.utils import build_payload, parse_payload from semantic_kernel.exceptions import ( ServiceInitializationError, ServiceInvalidRequestError, @@ -53,10 +50,10 @@ def __init__( ) -> None: """Initializes a new instance of the PineconeMemoryStore class. - Arguments: - pinecone_api_key {str} -- The Pinecone API key. - default_dimensionality {int} -- The default dimensionality to use for new collections. - env_file_path {str | None} -- Use the environment settings file as a fallback + Args: + api_key (str): The Pinecone API key. + default_dimensionality (int): The default dimensionality to use for new collections. + env_file_path (str | None): Use the environment settings file as a fallback to environment variables. (Optional) """ if default_dimensionality > MAX_DIMENSIONALITY: @@ -74,7 +71,8 @@ def __init__( api_key = api_key or ( pinecone_settings.api_key.get_secret_value() if pinecone_settings and pinecone_settings.api_key else None ) - assert api_key, "The Pinecone api_key cannot be None." + if not api_key: + raise ValueError("The Pinecone api_key cannot be None.") self._pinecone_api_key = api_key self._default_dimensionality = default_dimensionality @@ -90,16 +88,18 @@ async def create_collection( index_spec: NamedTuple = DEFAULT_INDEX_SPEC, ) -> None: """Creates a new collection in Pinecone if it does not exist. - This function creates an index, by default the following index - settings are used: metric = cosine, cloud = aws, region = us-east-1. - - Arguments: - collection_name {str} -- The name of the collection to create. - In Pinecone, a collection is represented as an index. The concept - of "collection" in Pinecone is just a static copy of an index. - Returns: - None + This function creates an index, by default the following index + settings are used: metric = cosine, cloud = aws, region = us-east-1. + + Args: + collection_name (str): The name of the collection to create. + In Pinecone, a collection is represented as an index. The concept + of "collection" in Pinecone is just a static copy of an index. + dimension_num (int, optional): The dimensionality of the embeddings. + distance_type (str, optional): The distance metric to use for the index. + (default: {"cosine"}) + index_spec (NamedTuple, optional): The index spec to use for the index. """ if dimension_num is None: dimension_num = self._default_dimensionality @@ -116,10 +116,12 @@ async def create_collection( async def describe_collection(self, collection_name: str) -> IndexDescription | None: """Gets the description of the index. - Arguments: - collection_name {str} -- The name of the index to get. + + Args: + collection_name (str): The name of the index to get. + Returns: - Optional[dict] -- The index. + Optional[dict]: The index. """ if await self.does_collection_exist(collection_name): return self.pinecone.describe_index(collection_name) @@ -131,15 +133,15 @@ async def get_collections( """Gets the list of collections. Returns: - IndexList -- The list of collections. + IndexList: The list of collections. """ return self.pinecone.list_indexes() async def delete_collection(self, collection_name: str) -> None: """Deletes a collection. - Arguments: - collection_name {str} -- The name of the collection to delete. + Args: + collection_name (str): The name of the collection to delete. Returns: None @@ -151,11 +153,11 @@ async def delete_collection(self, collection_name: str) -> None: async def does_collection_exist(self, collection_name: str) -> bool: """Checks if a collection exists. - Arguments: - collection_name {str} -- The name of the collection to check. + Args: + collection_name (str): The name of the collection to check. Returns: - bool -- True if the collection exists; otherwise, False. + bool: True if the collection exists; otherwise, False. """ if collection_name in self.collection_names_cache: return True @@ -166,14 +168,14 @@ async def does_collection_exist(self, collection_name: str) -> bool: return collection_name in index_collection_names async def upsert(self, collection_name: str, record: MemoryRecord) -> str: - """Upserts a record. + """Upsert a record. - Arguments: - collection_name {str} -- The name of the collection to upsert the record into. - record {MemoryRecord} -- The record to upsert. + Args: + collection_name (str): The name of the collection to upsert the record into. + record (MemoryRecord): The record to upsert. Returns: - str -- The unique database key of the record. In Pinecone, this is the record ID. + str: The unique database key of the record. In Pinecone, this is the record ID. """ if not await self.does_collection_exist(collection_name): raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist") @@ -191,14 +193,14 @@ async def upsert(self, collection_name: str, record: MemoryRecord) -> str: return record._id async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: - """Upserts a batch of records. + """Upsert a batch of records. - Arguments: - collection_name {str} -- The name of the collection to upsert the records into. - records {List[MemoryRecord]} -- The records to upsert. + Args: + collection_name (str): The name of the collection to upsert the records into. + records (List[MemoryRecord]): The records to upsert. Returns: - List[str] -- The unique database keys of the records. + List[str]: The unique database keys of the records. """ if not await self.does_collection_exist(collection_name): raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist") @@ -224,13 +226,13 @@ async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord: """Gets a record. - Arguments: - collection_name {str} -- The name of the collection to get the record from. - key {str} -- The unique database key of the record. - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the record from. + key (str): The unique database key of the record. + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - MemoryRecord -- The record. + MemoryRecord: The record. """ if not await self.does_collection_exist(collection_name): raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist") @@ -248,13 +250,13 @@ async def get_batch( ) -> list[MemoryRecord]: """Gets a batch of records. - Arguments: - collection_name {str} -- The name of the collection to get the records from. - keys {List[str]} -- The unique database keys of the records. - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + Args: + collection_name (str): The name of the collection to get the records from. + keys (List[str]): The unique database keys of the records. + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[MemoryRecord] -- The records. + List[MemoryRecord]: The records. """ if not await self.does_collection_exist(collection_name): raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist") @@ -265,9 +267,9 @@ async def get_batch( async def remove(self, collection_name: str, key: str) -> None: """Removes a record. - Arguments: - collection_name {str} -- The name of the collection to remove the record from. - key {str} -- The unique database key of the record to remove. + Args: + collection_name (str): The name of the collection to remove the record from. + key (str): The unique database key of the record to remove. Returns: None @@ -281,9 +283,9 @@ async def remove(self, collection_name: str, key: str) -> None: async def remove_batch(self, collection_name: str, keys: list[str]) -> None: """Removes a batch of records. - Arguments: - collection_name {str} -- The name of the collection to remove the records from. - keys {List[str]} -- The unique database keys of the records to remove. + Args: + collection_name (str): The name of the collection to remove the records from. + keys (List[str]): The unique database keys of the records to remove. Returns: None @@ -305,14 +307,14 @@ async def get_nearest_match( ) -> tuple[MemoryRecord, float]: """Gets the nearest match to an embedding using cosine similarity. - Arguments: - collection_name {str} -- The name of the collection to get the nearest match from. - embedding {ndarray} -- The embedding to find the nearest match to. - min_relevance_score {float} -- The minimum relevance score of the match. (default: {0.0}) - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the nearest match from. + embedding (ndarray): The embedding to find the nearest match to. + min_relevance_score (float): The minimum relevance score of the match. (default: {0.0}) + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - Tuple[MemoryRecord, float] -- The record and the relevance score. + Tuple[MemoryRecord, float]: The record and the relevance score. """ matches = await self.get_nearest_matches( collection_name=collection_name, @@ -333,15 +335,15 @@ async def get_nearest_matches( ) -> list[tuple[MemoryRecord, float]]: """Gets the nearest matches to an embedding using cosine similarity. - Arguments: - collection_name {str} -- The name of the collection to get the nearest matches from. - embedding {ndarray} -- The embedding to find the nearest matches to. - limit {int} -- The maximum number of matches to return. - min_relevance_score {float} -- The minimum relevance score of the matches. (default: {0.0}) - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + Args: + collection_name (str): The name of the collection to get the nearest matches from. + embedding (ndarray): The embedding to find the nearest matches to. + limit (int): The maximum number of matches to return. + min_relevance_score (float): The minimum relevance score of the matches. (default: {0.0}) + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[Tuple[MemoryRecord, float]] -- The records and their relevance scores. + List[Tuple[MemoryRecord, float]]: The records and their relevance scores. """ if not await self.does_collection_exist(collection_name): raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist") diff --git a/python/semantic_kernel/connectors/memory/pinecone/pinecone_settings.py b/python/semantic_kernel/connectors/memory/pinecone/pinecone_settings.py index ca8cd10e7ee4..efd5331548ed 100644 --- a/python/semantic_kernel/connectors/memory/pinecone/pinecone_settings.py +++ b/python/semantic_kernel/connectors/memory/pinecone/pinecone_settings.py @@ -8,9 +8,9 @@ @experimental_class class PineconeSettings(BaseModelSettings): - """Pinecone model settings + """Pinecone model settings. - Required: + Args: - api_key: SecretStr - Pinecone API key (Env var PINECONE_API_KEY) """ @@ -18,4 +18,6 @@ class PineconeSettings(BaseModelSettings): api_key: SecretStr | None = None class Config(BaseModelSettings.Config): + """Config for Pinecone settings.""" + env_prefix = "PINECONE_" diff --git a/python/semantic_kernel/connectors/memory/pinecone/utils.py b/python/semantic_kernel/connectors/memory/pinecone/utils.py index 218c2035aff1..e89dbe567a36 100644 --- a/python/semantic_kernel/connectors/memory/pinecone/utils.py +++ b/python/semantic_kernel/connectors/memory/pinecone/utils.py @@ -7,9 +7,7 @@ def build_payload(record: MemoryRecord) -> dict: - """ - Builds a metadata payload to be sent to Pinecone from a MemoryRecord. - """ + """Builds a metadata payload to be sent to Pinecone from a MemoryRecord.""" payload: dict = {} if record._text: payload["text"] = record._text @@ -21,9 +19,7 @@ def build_payload(record: MemoryRecord) -> dict: def parse_payload(record: Vector, with_embeddings: bool) -> MemoryRecord: - """ - Parses a record from Pinecone into a MemoryRecord. - """ + """Parses a record from Pinecone into a MemoryRecord.""" payload = record.metadata description = payload.get("description", None) text = payload.get("text", None) diff --git a/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py b/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py index eb99c5b7f197..14e68cd6c1ec 100644 --- a/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py +++ b/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py @@ -48,15 +48,13 @@ def __init__( ) -> None: """Initializes a new instance of the PostgresMemoryStore class. - Arguments: - connection_string {str} -- The connection string to the Postgres database.\n - default_dimensionality {int} -- The default dimensionality of the embeddings.\n - min_pool {int} -- The minimum number of connections in the connection pool.\n - max_pool {int} -- The maximum number of connections in the connection pool.\n - schema {str} -- The schema to use. (default: {"public"})\n - timezone_offset {Optional[str]} -- The timezone offset to use. (default: {None}) - Expected format '-7:00'. Uses the local timezone offset when not provided.\n - env_file_path {str | None} -- Use the environment settings file as a fallback + Args: + connection_string (str): The connection string to the Postgres database. + default_dimensionality (int): The default dimensionality of the embeddings. + min_pool (int): The minimum number of connections in the connection pool. + max_pool (int): The maximum number of connections in the connection pool. + schema (str): The schema to use. (default: {"public"}) + env_file_path (str | None): Use the environment settings file as a fallback to environment variables. (Optional) """ postgres_settings = None @@ -84,11 +82,11 @@ async def create_collection( collection_name: str, dimension_num: int | None = None, ) -> None: - """Creates a new collection. + r"""Creates a new collection. - Arguments: - collection_name {str} -- The name of the collection to create.\n - dimension_num {Optional[int]} -- The dimensionality of the embeddings. (default: {None}) + Args: + collection_name (str): The name of the collection to create.\n + dimension_num (Optional[int]): The dimensionality of the embeddings. (default: {None}) Uses the default dimensionality when not provided Returns: @@ -122,7 +120,7 @@ async def get_collections(self) -> list[str]: """Gets the list of collections. Returns: - List[str] -- The list of collections. + List[str]: The list of collections. """ with self._connection_pool.connection() as conn: with conn.cursor() as cur: @@ -131,8 +129,8 @@ async def get_collections(self) -> list[str]: async def delete_collection(self, collection_name: str) -> None: """Deletes a collection. - Arguments: - collection_name {str} -- The name of the collection to delete. + Args: + collection_name (str): The name of the collection to delete. Returns: None @@ -148,25 +146,25 @@ async def delete_collection(self, collection_name: str) -> None: async def does_collection_exist(self, collection_name: str) -> bool: """Checks if a collection exists. - Arguments: - collection_name {str} -- The name of the collection to check. + Args: + collection_name (str): The name of the collection to check. Returns: - bool -- True if the collection exists; otherwise, False. + bool: True if the collection exists; otherwise, False. """ with self._connection_pool.connection() as conn: with conn.cursor() as cur: return await self.__does_collection_exist(cur, collection_name) async def upsert(self, collection_name: str, record: MemoryRecord) -> str: - """Upserts a record. + r"""Upserts a record. - Arguments: - collection_name {str} -- The name of the collection to upsert the record into.\n - record {MemoryRecord} -- The record to upsert. + Args: + collection_name (str): The name of the collection to upsert the record into.\n + record (MemoryRecord): The record to upsert. Returns: - str -- The unique database key of the record. In Pinecone, this is the record ID. + str: The unique database key of the record. In Pinecone, this is the record ID. """ with self._connection_pool.connection() as conn: with conn.cursor() as cur: @@ -202,12 +200,12 @@ async def upsert(self, collection_name: str, record: MemoryRecord) -> str: async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: """Upserts a batch of records. - Arguments: - collection_name {str} -- The name of the collection to upsert the records into. - records {List[MemoryRecord]} -- The records to upsert. + Args: + collection_name (str): The name of the collection to upsert the records into. + records (List[MemoryRecord]): The records to upsert. Returns: - List[str] -- The unique database keys of the records. + List[str]: The unique database keys of the records. """ with self._connection_pool.connection() as conn: with conn.cursor() as cur: @@ -252,13 +250,13 @@ async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord: """Gets a record. - Arguments: - collection_name {str} -- The name of the collection to get the record from. - key {str} -- The unique database key of the record. - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the record from. + key (str): The unique database key of the record. + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - MemoryRecord -- The record. + MemoryRecord: The record. """ with self._connection_pool.connection() as conn: with conn.cursor() as cur: @@ -296,13 +294,13 @@ async def get_batch( ) -> list[MemoryRecord]: """Gets a batch of records. - Arguments: - collection_name {str} -- The name of the collection to get the records from. - keys {List[str]} -- The unique database keys of the records. - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + Args: + collection_name (str): The name of the collection to get the records from. + keys (List[str]): The unique database keys of the records. + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[MemoryRecord] -- The records that were found from list of keys, can be empty. + List[MemoryRecord]: The records that were found from list of keys, can be empty. """ with self._connection_pool.connection() as conn: with conn.cursor() as cur: @@ -341,9 +339,9 @@ async def get_batch( async def remove(self, collection_name: str, key: str) -> None: """Removes a record. - Arguments: - collection_name {str} -- The name of the collection to remove the record from. - key {str} -- The unique database key of the record to remove. + Args: + collection_name (str): The name of the collection to remove the record from. + key (str): The unique database key of the record to remove. Returns: None @@ -365,9 +363,9 @@ async def remove(self, collection_name: str, key: str) -> None: async def remove_batch(self, collection_name: str, keys: list[str]) -> None: """Removes a batch of records. - Arguments: - collection_name {str} -- The name of the collection to remove the records from. - keys {List[str]} -- The unique database keys of the records to remove. + Args: + collection_name (str): The name of the collection to remove the records from. + keys (List[str]): The unique database keys of the records to remove. Returns: None @@ -396,15 +394,15 @@ async def get_nearest_matches( ) -> list[tuple[MemoryRecord, float]]: """Gets the nearest matches to an embedding using cosine similarity. - Arguments: - collection_name {str} -- The name of the collection to get the nearest matches from. - embedding {ndarray} -- The embedding to find the nearest matches to. - limit {int} -- The maximum number of matches to return. - min_relevance_score {float} -- The minimum relevance score of the matches. (default: {0.0}) - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + Args: + collection_name (str): The name of the collection to get the nearest matches from. + embedding (ndarray): The embedding to find the nearest matches to. + limit (int): The maximum number of matches to return. + min_relevance_score (float): The minimum relevance score of the matches. (default: {0.0}) + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[Tuple[MemoryRecord, float]] -- The records and their relevance scores. + List[Tuple[MemoryRecord, float]]: The records and their relevance scores. """ with self._connection_pool.connection() as conn: with conn.cursor() as cur: @@ -465,16 +463,15 @@ async def get_nearest_match( ) -> tuple[MemoryRecord, float]: """Gets the nearest match to an embedding using cosine similarity. - Arguments: - collection_name {str} -- The name of the collection to get the nearest match from. - embedding {ndarray} -- The embedding to find the nearest match to. - min_relevance_score {float} -- The minimum relevance score of the match. (default: {0.0}) - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the nearest match from. + embedding (ndarray): The embedding to find the nearest match to. + min_relevance_score (float): The minimum relevance score of the match. (default: {0.0}) + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - Tuple[MemoryRecord, float] -- The record and the relevance score. + Tuple[MemoryRecord, float]: The record and the relevance score. """ - results = await self.get_nearest_matches( collection_name=collection_name, embedding=embedding, diff --git a/python/semantic_kernel/connectors/memory/postgres/postgres_settings.py b/python/semantic_kernel/connectors/memory/postgres/postgres_settings.py index 10597cb48ace..207e2dcdcbdf 100644 --- a/python/semantic_kernel/connectors/memory/postgres/postgres_settings.py +++ b/python/semantic_kernel/connectors/memory/postgres/postgres_settings.py @@ -8,9 +8,9 @@ @experimental_class class PostgresSettings(BaseModelSettings): - """Postgres model settings + """Postgres model settings. - Required: + Args: - connection_string: str - Postgres connection string (Env var POSTGRES_CONNECTION_STRING) """ @@ -18,4 +18,6 @@ class PostgresSettings(BaseModelSettings): connection_string: SecretStr | None = None class Config(BaseModelSettings.Config): + """Config for Postgres settings.""" + env_prefix = "POSTGRES_" diff --git a/python/semantic_kernel/connectors/memory/qdrant/qdrant_memory_store.py b/python/semantic_kernel/connectors/memory/qdrant/qdrant_memory_store.py index 380924cddf7d..a1d88046b46c 100644 --- a/python/semantic_kernel/connectors/memory/qdrant/qdrant_memory_store.py +++ b/python/semantic_kernel/connectors/memory/qdrant/qdrant_memory_store.py @@ -1,18 +1,19 @@ # Copyright (c) Microsoft. All rights reserved. -""" -QdrantMemoryStore provides functionality to add Qdrant vector database to support Semantic Kernel memory. -The QdrantMemoryStore inherits from MemoryStoreBase for persisting/retrieving data from a Qdrant Vector Database. -""" - import asyncio import logging +import sys import uuid from numpy import ndarray from qdrant_client import QdrantClient from qdrant_client import models as qdrant_models +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + from semantic_kernel.exceptions import ServiceResponseException from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase @@ -34,8 +35,6 @@ def __init__( **kwargs, ) -> None: """Initializes a new instance of the QdrantMemoryStore class.""" - if kwargs.get("logger"): - logger.warning("The `logger` parameter is deprecated. Please use the `logging` module instead.") if local: if url: self._qdrantclient = QdrantClient(location=url) @@ -46,16 +45,8 @@ def __init__( self._default_vector_size = vector_size + @override async def create_collection(self, collection_name: str) -> None: - """Creates a new collection if it does not exist. - - Arguments: - collection_name {str} -- The name of the collection to create. - vector_size {int} -- The size of the vector. - distance {Optional[str]} -- The distance metric to use. (default: {"Cosine"}) - Returns: - None - """ self._qdrantclient.recreate_collection( collection_name=collection_name, vectors_config=qdrant_models.VectorParams( @@ -63,14 +54,10 @@ async def create_collection(self, collection_name: str) -> None: ), ) + @override async def get_collections( self, ) -> list[str]: - """Gets the list of collections. - - Returns: - List[str] -- The list of collections. - """ collection_info = self._qdrantclient.get_collections() return [collection.name for collection in collection_info.collections] @@ -83,43 +70,20 @@ async def get_collection(self, collection_name: str) -> qdrant_models.Collection collection_info = self._qdrantclient.get_collection(collection_name=collection_name) return collection_info + @override async def delete_collection(self, collection_name: str) -> None: - """Deletes a collection. - - Arguments: - collection_name {str} -- The name of the collection to delete. - - Returns: - None - """ - self._qdrantclient.delete_collection(collection_name=collection_name) + @override async def does_collection_exist(self, collection_name: str) -> bool: - """Checks if a collection exists. - - Arguments: - collection_name {str} -- The name of the collection to check. - - Returns: - bool -- True if the collection exists; otherwise, False. - """ try: result = await self.get_collection(collection_name=collection_name) return result.status == qdrant_models.CollectionStatus.GREEN except ValueError: return False + @override async def upsert(self, collection_name: str, record: MemoryRecord) -> str: - """Upserts a record. - - Arguments: - collection_name {str} -- The name of the collection to upsert the record into. - record {MemoryRecord} -- The record to upsert. - - Returns: - str -- The unique database key of the record. - """ data_to_upsert = await self._convert_from_memory_record( collection_name=collection_name, record=record, @@ -135,6 +99,7 @@ async def upsert(self, collection_name: str, record: MemoryRecord) -> str: else: raise ServiceResponseException("Upsert failed") + @override async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: tasks = [] for record in records: @@ -157,6 +122,7 @@ async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) else: raise ServiceResponseException("Batch upsert failed") + @override async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord | None: result = await self._get_existing_record_by_payload_id( collection_name=collection_name, @@ -179,8 +145,12 @@ async def get(self, collection_name: str, key: str, with_embedding: bool = False else: return None + @override async def get_batch( - self, collection_name: str, keys: list[str], with_embeddings: bool = False + self, + collection_name: str, + keys: list[str], + with_embeddings: bool = False, ) -> list[MemoryRecord]: tasks = [] for key in keys: @@ -193,6 +163,7 @@ async def get_batch( ) return await asyncio.gather(*tasks) + @override async def remove(self, collection_name: str, key: str) -> None: existing_record = await self._get_existing_record_by_payload_id( collection_name=collection_name, @@ -206,6 +177,7 @@ async def remove(self, collection_name: str, key: str) -> None: if result.status != qdrant_models.UpdateStatus.COMPLETED: raise ServiceResponseException("Delete failed") + @override async def remove_batch(self, collection_name: str, keys: list[str]) -> None: tasks = [] for key in keys: @@ -227,6 +199,7 @@ async def remove_batch(self, collection_name: str, keys: list[str]) -> None: if result.status != qdrant_models.UpdateStatus.COMPLETED: raise ServiceResponseException("Delete failed") + @override async def get_nearest_matches( self, collection_name: str, @@ -261,6 +234,7 @@ async def get_nearest_matches( for result in match_results ] + @override async def get_nearest_match( self, collection_name: str, @@ -285,12 +259,13 @@ async def _get_existing_record_by_payload_id( ) -> qdrant_models.ScoredPoint | None: """Gets an existing record based upon payload id. - Arguments: - collection_name {str} -- The name of the collection. - payload_id {str} -- The payload id to search for. + Args: + collection_name (str): The name of the collection. + payload_id (str): The payload id to search for. + with_embedding (bool): If true, the embedding will be returned in the memory records. Returns: - Optional[ScoredPoint] -- The existing record if found; otherwise, None. + Optional[ScoredPoint]: The existing record if found; otherwise, None. """ filter = qdrant_models.Filter( must=[ diff --git a/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py b/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py index 2d0f6a9f1340..34617b7710d3 100644 --- a/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py +++ b/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py @@ -32,7 +32,7 @@ @experimental_class class RedisMemoryStore(MemoryStoreBase): - """A memory store implementation using Redis""" + """A memory store implementation using Redis.""" _database: "redis.Redis" _ft: "redis.Redis.ft" @@ -55,19 +55,19 @@ def __init__( query_dialect: int = 2, env_file_path: str | None = None, ) -> None: - """ - RedisMemoryStore is an abstracted interface to interact with a Redis node connection. + """RedisMemoryStore is an abstracted interface to interact with a Redis node connection. + See documentation about connections: https://redis-py.readthedocs.io/en/stable/connections.html - See documentation about vector attributes: https://redis.io/docs/stack/search/reference/vectors - - Arguments: - connection_string {str} -- Provide connection URL to a Redis instance - vector_size {str} -- Size of vectors, defaults to 1536 - vector_distance_metric {str} -- Metric for measuring vector distances, defaults to COSINE - vector_type {str} -- Vector type, defaults to FLOAT32 - vector_index_algorithm {str} -- Indexing algorithm for vectors, defaults to HNSW - query_dialect {int} -- Query dialect, must be 2 or greater for vector similarity searching, defaults to 2 - env_file_path {str | None} -- Use the environment settings file as a fallback to + See documentation about vector attributes: https://redis.io/docs/stack/search/reference/vectors. + + Args: + connection_string (str): Provide connection URL to a Redis instance + vector_size (str): Size of vectors, defaults to 1536 + vector_distance_metric (str): Metric for measuring vector distances, defaults to COSINE + vector_type (str): Vector type, defaults to FLOAT32 + vector_index_algorithm (str): Indexing algorithm for vectors, defaults to HNSW + query_dialect (int): Query dialect, must be 2 or greater for vector similarity searching, defaults to 2 + env_file_path (str | None): Use the environment settings file as a fallback to environment variables, defaults to False """ redis_settings = None @@ -96,22 +96,19 @@ def __init__( self._vector_size = vector_size async def close(self): - """ - Closes the Redis database connection - """ + """Closes the Redis database connection.""" logger.info("Closing Redis connection") self._database.close() async def create_collection(self, collection_name: str) -> None: - """ - Creates a collection, implemented as a Redis index containing hashes - prefixed with "collection_name:". + """Creates a collection. + + Implemented as a Redis index containing hashes prefixed with "collection_name:". If a collection of the name exists, it is left unchanged. - Arguments: - collection_name {str} -- Name for a collection of embeddings + Args: + collection_name (str): Name for a collection of embeddings """ - if await self.does_collection_exist(collection_name): logger.info(f'Collection "{collection_name}" already exists.') else: @@ -137,34 +134,31 @@ async def create_collection(self, collection_name: str) -> None: raise ServiceResponseException(f"Failed to create collection {collection_name}") from e async def get_collections(self) -> list[str]: - """ - Returns a list of names of all collection names present in the data store. + """Returns a list of names of all collection names present in the data store. Returns: - List[str] -- list of collection names + List[str]: list of collection names """ # Note: FT._LIST is a temporary command that may be deprecated in the future according to Redis return [name.decode() for name in self._database.execute_command("FT._LIST")] async def delete_collection(self, collection_name: str, delete_records: bool = True) -> None: - """ - Deletes a collection from the data store. - If the collection does not exist, the database is left unchanged. + """Deletes a collection from the data store. - Arguments: - collection_name {str} -- Name for a collection of embeddings - delete_records {bool} -- Delete all data associated with the collection, default to True + If the collection does not exist, the database is left unchanged. + Args: + collection_name (str): Name for a collection of embeddings + delete_records (bool): Delete all data associated with the collection, default to True """ if await self.does_collection_exist(collection_name): self._ft(collection_name).dropindex(delete_documents=delete_records) async def does_collection_exist(self, collection_name: str) -> bool: - """ - Determines if a collection exists in the data store. + """Determines if a collection exists in the data store. - Arguments: - collection_name {str} -- Name for a collection of embeddings + Args: + collection_name (str): Name for a collection of embeddings Returns: True if the collection exists, False if not @@ -176,22 +170,22 @@ async def does_collection_exist(self, collection_name: str) -> bool: return False async def upsert(self, collection_name: str, record: MemoryRecord) -> str: - """ - Upsert a memory record into the data store. Does not guarantee that the collection exists. + """Upsert a memory record into the data store. + + Does not guarantee that the collection exists. * If the record already exists, it will be updated. * If the record does not exist, it will be created. Note: if the record do not have the same dimensionality configured for the collection, it will not be detected to belong to the collection in Redis. - Arguments: - collection_name {str} -- Name for a collection of embeddings - record {MemoryRecord} -- Memory record to upsert + Args: + collection_name (str): Name for a collection of embeddings + record (MemoryRecord): Memory record to upsert - Returns - str -- Redis key associated with the upserted memory record + Returns: + str: Redis key associated with the upserted memory record """ - if not await self.does_collection_exist(collection_name): raise ServiceResourceNotFoundError(f'Collection "{collection_name}" does not exist') @@ -210,22 +204,22 @@ async def upsert(self, collection_name: str, record: MemoryRecord) -> str: raise ServiceResponseException("Could not upsert messages.") from e async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: - """ - Upserts a group of memory records into the data store. Does not guarantee that the collection exists. + """Upserts a group of memory records into the data store. + + Does not guarantee that the collection exists. * If the record already exists, it will be updated. * If the record does not exist, it will be created. Note: if the records do not have the same dimensionality configured for the collection, they will not be detected to belong to the collection in Redis. - Arguments: - collection_name {str} -- Name for a collection of embeddings - records {List[MemoryRecord]} -- List of memory records to upsert + Args: + collection_name (str): Name for a collection of embeddings + records (List[MemoryRecord]): List of memory records to upsert - Returns - List[str] -- Redis keys associated with the upserted memory records + Returns: + List[str]: Redis keys associated with the upserted memory records """ - keys = list() for record in records: record_key = await self.upsert(collection_name, record) @@ -234,18 +228,16 @@ async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) return keys async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord: - """ - Gets a memory record from the data store. Does not guarantee that the collection exists. + """Gets a memory record from the data store. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- Name for a collection of embeddings - key {str} -- ID associated with the memory to get - with_embedding {bool} -- Include embedding with the memory record, default to False + Args: + collection_name (str): Name for a collection of embeddings + key (str): ID associated with the memory to get + with_embedding (bool): Include embedding with the memory record, default to False Returns: - MemoryRecord -- The memory record if found, else None + MemoryRecord: The memory record if found, else None """ - if not await self.does_collection_exist(collection_name): raise ServiceResourceNotFoundError(f'Collection "{collection_name}" does not exist') @@ -264,18 +256,18 @@ async def get(self, collection_name: str, key: str, with_embedding: bool = False async def get_batch( self, collection_name: str, keys: list[str], with_embeddings: bool = False ) -> list[MemoryRecord]: - """ - Gets a batch of memory records from the data store. Does not guarantee that the collection exists. + """Gets a batch of memory records from the data store. - Arguments: - collection_name {str} -- Name for a collection of embeddings - keys {List[str]} -- IDs associated with the memory records to get - with_embedding {bool} -- Include embeddings with the memory records, default to False + Does not guarantee that the collection exists. + + Args: + collection_name (str): Name for a collection of embeddings + keys (List[str]): IDs associated with the memory records to get + with_embeddings (bool): Include embeddings with the memory records, default to False Returns: - List[MemoryRecord] -- The memory records if found, else an empty list + List[MemoryRecord]: The memory records if found, else an empty list """ - records = list() for key in keys: record = await self.get(collection_name, key, with_embeddings) @@ -285,13 +277,14 @@ async def get_batch( return records async def remove(self, collection_name: str, key: str) -> None: - """ - Removes a memory record from the data store. Does not guarantee that the collection exists. + """Removes a memory record from the data store. + + Does not guarantee that the collection exists. If the key does not exist, do nothing. - Arguments: - collection_name {str} -- Name for a collection of embeddings - key {str} -- ID associated with the memory to remove + Args: + collection_name (str): Name for a collection of embeddings + key (str): ID associated with the memory to remove """ if not await self.does_collection_exist(collection_name): raise ServiceResourceNotFoundError(f'Collection "{collection_name}" does not exist') @@ -299,12 +292,11 @@ async def remove(self, collection_name: str, key: str) -> None: self._database.delete(get_redis_key(collection_name, key)) async def remove_batch(self, collection_name: str, keys: list[str]) -> None: - """ - Removes a batch of memory records from the data store. Does not guarantee that the collection exists. + """Removes a batch of memory records from the data store. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- Name for a collection of embeddings - keys {List[str]} -- IDs associated with the memory records to remove + Args: + collection_name (str): Name for a collection of embeddings + keys (List[str]): IDs associated with the memory records to remove """ if not await self.does_collection_exist(collection_name): raise ServiceResourceNotFoundError(f'Collection "{collection_name}" does not exist') @@ -319,18 +311,17 @@ async def get_nearest_matches( min_relevance_score: float = 0.0, with_embeddings: bool = False, ) -> list[tuple[MemoryRecord, float]]: - """ - Get the nearest matches to an embedding using the configured similarity algorithm. + """Get the nearest matches to an embedding using the configured similarity algorithm. - Arguments: - collection_name {str} -- Name for a collection of embeddings - embedding {ndarray} -- Embedding to find the nearest matches to - limit {int} -- Maximum number of matches to return - min_relevance_score {float} -- Minimum relevance score of the matches, default to 0.0 - with_embeddings {bool} -- Include embeddings in the resultant memory records, default to False + Args: + collection_name (str): Name for a collection of embeddings + embedding (ndarray): Embedding to find the nearest matches to + limit (int): Maximum number of matches to return + min_relevance_score (float): Minimum relevance score of the matches, default to 0.0 + with_embeddings (bool): Include embeddings in the resultant memory records, default to False Returns: - List[Tuple[MemoryRecord, float]] -- Records and their relevance scores by descending + List[Tuple[MemoryRecord, float]]: Records and their relevance scores by descending order, or an empty list if no relevant matches are found """ if not await self.does_collection_exist(collection_name): @@ -372,17 +363,16 @@ async def get_nearest_match( min_relevance_score: float = 0.0, with_embedding: bool = False, ) -> tuple[MemoryRecord, float]: - """ - Get the nearest match to an embedding using the configured similarity algorithm. + """Get the nearest match to an embedding using the configured similarity algorithm. - Arguments: - collection_name {str} -- Name for a collection of embeddings - embedding {ndarray} -- Embedding to find the nearest match to - min_relevance_score {float} -- Minimum relevance score of the match, default to 0.0 - with_embedding {bool} -- Include embedding in the resultant memory record, default to False + Args: + collection_name (str): Name for a collection of embeddings + embedding (ndarray): Embedding to find the nearest match to + min_relevance_score (float): Minimum relevance score of the match, default to 0.0 + with_embedding (bool): Include embedding in the resultant memory record, default to False Returns: - Tuple[MemoryRecord, float] -- Record and the relevance score, or None if not found + Tuple[MemoryRecord, float]: Record and the relevance score, or None if not found """ matches = await self.get_nearest_matches( collection_name=collection_name, diff --git a/python/semantic_kernel/connectors/memory/redis/redis_settings.py b/python/semantic_kernel/connectors/memory/redis/redis_settings.py index 837d085fd906..aa7220fa2eb5 100644 --- a/python/semantic_kernel/connectors/memory/redis/redis_settings.py +++ b/python/semantic_kernel/connectors/memory/redis/redis_settings.py @@ -8,14 +8,16 @@ @experimental_class class RedisSettings(BaseModelSettings): - """Redis model settings + """Redis model settings. - Optional: - - connection_string: str | None - Redis connection string - (Env var REDIS_CONNECTION_STRING) + Args: + - connection_string (str | None): + Redis connection string (Env var REDIS_CONNECTION_STRING) """ connection_string: SecretStr | None = None class Config(BaseModelSettings.Config): + """Model configuration.""" + env_prefix = "REDIS_" diff --git a/python/semantic_kernel/connectors/memory/redis/utils.py b/python/semantic_kernel/connectors/memory/redis/utils.py index 7577d35bf1d8..4108e6b09387 100644 --- a/python/semantic_kernel/connectors/memory/redis/utils.py +++ b/python/semantic_kernel/connectors/memory/redis/utils.py @@ -12,34 +12,33 @@ def get_redis_key(collection_name: str, record_id: str) -> str: - """ - Returns the Redis key for an element called record_id within collection_name + """Returns the Redis key for an element called record_id within collection_name. - Arguments: - collection_name {str} -- Name for a collection of embeddings - record_id {str} -- ID associated with a memory record + Args: + collection_name (str): Name for a collection of embeddings + record_id (str): ID associated with a memory record Returns: - str -- Redis key in the format collection_name:id + str: Redis key in the format collection_name:id """ return f"{collection_name}:{record_id}" def split_redis_key(redis_key: str) -> tuple[str, str]: - """ - Split a Redis key into its collection name and record ID + """Split a Redis key into its collection name and record ID. - Arguments: - collection_name {str} -- Redis key + Args: + redis_key (str): Redis key Returns: - Tuple[str, str] -- Tuple of the collection name and ID + tuple[str, str]: Tuple of the collection name and ID """ collection, record_id = redis_key.split(":") return collection, record_id def serialize_record_to_redis(record: MemoryRecord, vector_type: np.dtype) -> dict[str, Any]: + """Serialize a MemoryRecord to Redis fields.""" all_metadata = { "is_reference": record._is_reference, "external_source_name": record._external_source_name or "", @@ -59,6 +58,7 @@ def serialize_record_to_redis(record: MemoryRecord, vector_type: np.dtype) -> di def deserialize_redis_to_record(fields: dict[str, Any], vector_type: np.dtype, with_embedding: bool) -> MemoryRecord: + """Deserialize Redis fields to a MemoryRecord.""" metadata = json.loads(fields[b"metadata"]) record = MemoryRecord( id=metadata["id"], @@ -83,6 +83,7 @@ def deserialize_redis_to_record(fields: dict[str, Any], vector_type: np.dtype, w def deserialize_document_to_record( database: Redis, doc: Document, vector_type: np.dtype, with_embedding: bool ) -> MemoryRecord: + """Deserialize document to a MemoryRecord.""" # Document's ID refers to the Redis key redis_key = doc["id"] _, id_str = split_redis_key(redis_key) diff --git a/python/semantic_kernel/connectors/memory/usearch/usearch_memory_store.py b/python/semantic_kernel/connectors/memory/usearch/usearch_memory_store.py index b0e11086b7fb..7fbafcadb898 100644 --- a/python/semantic_kernel/connectors/memory/usearch/usearch_memory_store.py +++ b/python/semantic_kernel/connectors/memory/usearch/usearch_memory_store.py @@ -90,7 +90,7 @@ class _CollectionFileType(Enum): def memoryrecords_to_pyarrow_table(records: list[MemoryRecord]) -> pa.Table: - """Convert a list of `MemoryRecord` to a PyArrow Table""" + """Convert a list of `MemoryRecord` to a PyArrow Table.""" records_pylist = [ {attr: getattr(record, "_" + attr) for attr in _embeddings_data_schema.names} for record in records ] @@ -122,8 +122,7 @@ def __init__( self, persist_directory: os.PathLike | None = None, ) -> None: - """ - Create a USearchMemoryStore instance. + """Create a USearchMemoryStore instance. This store helps searching embeddings with USearch, keeping collections in memory. To save collections to disk, provide the `persist_directory` param. @@ -144,8 +143,7 @@ def __init__( self._collections = self._read_collections_from_dir() def _get_collection_path(self, collection_name: str, *, file_type: _CollectionFileType) -> Path: - """ - Get the path for the given collection name and file type. + """Get the path for the given collection name and file type. Args: collection_name (str): Name of the collection. @@ -280,6 +278,7 @@ async def get_collections(self) -> list[str]: return list(self._collections.keys()) async def delete_collection(self, collection_name: str) -> None: + """Delete collection by name.""" collection_name = collection_name.lower() collection = self._collections.pop(collection_name, None) if collection: @@ -287,6 +286,7 @@ async def delete_collection(self, collection_name: str) -> None: return None async def does_collection_exist(self, collection_name: str) -> bool: + """Check if collection exists.""" collection_name = collection_name.lower() return collection_name in self._collections @@ -484,7 +484,7 @@ async def get_nearest_matches( limit (int): maximum amount of embeddings to search for. min_relevance_score (float, optional): The minimum relevance score for vectors. Supposed to be from 0 to 1. Only vectors with greater or equal relevance score are returned. Defaults to 0.0. - with_embedding (bool, optional): If True, include the embedding in the result. Defaults to True. + with_embeddings (bool, optional): If True, include the embedding in the result. Defaults to True. threads (int, optional): Optimal number of cores to use. Defaults to 0. exact (bool, optional): Perform exhaustive linear-time exact search. Defaults to False. log (Union[str, bool], optional): Whether to print the progress bar. Defaults to False. @@ -507,7 +507,7 @@ async def get_nearest_matches( log=log, ) - assert isinstance(result, Matches) + # assert isinstance(result, Matches) # nosec relevance_score = 1 / (result.distances + 1) filtered_labels = result.keys[np.where(relevance_score >= min_relevance_score)[0]] diff --git a/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py b/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py index 3a2164a76aba..1dd9a23b8dcb 100644 --- a/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py +++ b/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py @@ -75,10 +75,9 @@ class WeaviateConfig: @experimental_class class WeaviateMemoryStore(MemoryStoreBase): class FieldMapper: - """ - This inner class is responsible for mapping attribute names between - the SK's memory record and weaviate's schema. It provides methods - for converting between the two naming conventions. + """This maps attribute names between the SK's memory record and weaviate's schema. + + It provides methods for converting between the two naming conventions. """ SK_TO_WEAVIATE_MAPPING = { @@ -97,12 +96,14 @@ class FieldMapper: @classmethod def sk_to_weaviate(cls, sk_dict): + """Used to convert a MemoryRecord to a dict of attribute-values that can be used by Weaviate.""" return { cls.SK_TO_WEAVIATE_MAPPING.get(k, k): v for k, v in sk_dict.items() if k in cls.SK_TO_WEAVIATE_MAPPING } @classmethod def weaviate_to_sk(cls, weaviate_dict): + """Used to convert a Weaviate object to a dict that can be used to initialize a MemoryRecord.""" return { cls.WEAVIATE_TO_SK_MAPPING.get(k, k): v for k, v in weaviate_dict.items() @@ -111,18 +112,15 @@ def weaviate_to_sk(cls, weaviate_dict): @classmethod def remove_underscore_prefix(cls, sk_dict): - """ - Used to initialize a MemoryRecord from a SK's dict of private attribute-values. - """ + """Used to initialize a MemoryRecord from a SK's dict of private attribute-values.""" return {key.lstrip("_"): value for key, value in sk_dict.items()} def __init__(self, config: WeaviateConfig | None = None, env_file_path: str | None = None): - """Initializes a new instance of the WeaviateMemoryStore + """Initializes a new instance of the WeaviateMemoryStore. Optional parameters: - - env_file_path {str | None} -- Whether to use the environment settings (.env) file. Defaults to False. + - env_file_path (str | None): Whether to use the environment settings (.env) file. Defaults to False. """ - # Initialize settings from environment variables or defaults defined in WeaviateSettings weaviate_settings = None try: @@ -140,8 +138,7 @@ def __init__(self, config: WeaviateConfig | None = None, env_file_path: str | No self.client = self._initialize_client() def merge_settings(self, default_settings: WeaviateSettings, config: WeaviateConfig) -> WeaviateSettings: - """ - Merges default settings with configuration provided through WeaviateConfig. + """Merges default settings with configuration provided through WeaviateConfig. This function allows for manual overriding of settings from the config parameter. """ @@ -157,9 +154,7 @@ def merge_settings(self, default_settings: WeaviateSettings, config: WeaviateCon ) def _initialize_client(self) -> weaviate.Client: - """ - Initializes the Weaviate client based on the combined settings. - """ + """Initializes the Weaviate client based on the combined settings.""" if self.settings.use_embed: return weaviate.Client(embedded_options=weaviate.EmbeddedOptions()) @@ -171,22 +166,27 @@ def _initialize_client(self) -> weaviate.Client: return weaviate.Client(url=self.settings.url) async def create_collection(self, collection_name: str) -> None: + """Creates a new collection in Weaviate.""" schema = SCHEMA.copy() schema["class"] = collection_name await asyncio.get_running_loop().run_in_executor(None, self.client.schema.create_class, schema) async def get_collections(self) -> list[str]: + """Returns a list of all collections in Weaviate.""" schemas = await asyncio.get_running_loop().run_in_executor(None, self.client.schema.get) return [schema["class"] for schema in schemas["classes"]] async def delete_collection(self, collection_name: str) -> bool: + """Deletes a collection in Weaviate.""" await asyncio.get_running_loop().run_in_executor(None, self.client.schema.delete_class, collection_name) async def does_collection_exist(self, collection_name: str) -> bool: + """Checks if a collection exists in Weaviate.""" collections = await self.get_collections() return collection_name in collections async def upsert(self, collection_name: str, record: MemoryRecord) -> str: + """Upserts a record into Weaviate.""" weaviate_record = self.FieldMapper.sk_to_weaviate(vars(record)) vector = weaviate_record.pop("vector", None) @@ -202,6 +202,8 @@ async def upsert(self, collection_name: str, record: MemoryRecord) -> str: ) async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: + """Upserts a batch of records into Weaviate.""" + def _upsert_batch_inner(): results = [] with self.client.batch as batch: @@ -222,11 +224,13 @@ def _upsert_batch_inner(): return await asyncio.get_running_loop().run_in_executor(None, _upsert_batch_inner) async def get(self, collection_name: str, key: str, with_embedding: bool) -> MemoryRecord: + """Gets a record from Weaviate by key.""" # Call the batched version with a single key results = await self.get_batch(collection_name, [key], with_embedding) return results[0] if results else None async def get_batch(self, collection_name: str, keys: list[str], with_embedding: bool) -> list[MemoryRecord]: + """Gets a batch of records from Weaviate by keys.""" queries = self._build_multi_get_query(collection_name, keys, with_embedding) results = await asyncio.get_running_loop().run_in_executor(None, self.client.query.multi_get(queries).do) @@ -267,9 +271,11 @@ def _convert_weaviate_doc_to_memory_record(self, weaviate_doc: dict) -> MemoryRe return MemoryRecord(**mem_vals) async def remove(self, collection_name: str, key: str) -> None: + """Removes a record from Weaviate by key.""" await self.remove_batch(collection_name, [key]) async def remove_batch(self, collection_name: str, keys: list[str]) -> None: + """Removes a batch of records from Weaviate by keys.""" # TODO: Use In operator when it's available # (https://github.com/weaviate/weaviate/issues/2387) # and handle max delete objects @@ -293,6 +299,7 @@ async def get_nearest_matches( min_relevance_score: float, with_embeddings: bool, ) -> list[tuple[MemoryRecord, float]]: + """Gets the nearest matches to an embedding in Weaviate.""" nearVector = { "vector": embedding, "certainty": min_relevance_score, @@ -332,6 +339,7 @@ async def get_nearest_match( min_relevance_score: float, with_embedding: bool, ) -> tuple[MemoryRecord, float]: + """Gets the nearest match to an embedding in Weaviate.""" results = await self.get_nearest_matches( collection_name, embedding, diff --git a/python/semantic_kernel/connectors/memory/weaviate/weaviate_settings.py b/python/semantic_kernel/connectors/memory/weaviate/weaviate_settings.py index 1176880432ab..58e06ff341eb 100644 --- a/python/semantic_kernel/connectors/memory/weaviate/weaviate_settings.py +++ b/python/semantic_kernel/connectors/memory/weaviate/weaviate_settings.py @@ -9,13 +9,13 @@ @experimental_class class WeaviateSettings(BaseModelSettings): - """Weaviate model settings + """Weaviate model settings. - Optional: - - url: HttpsUrl | None - Weaviate URL (Env var WEAVIATE_URL) - - api_key: SecretStr | None - Weaviate token (Env var WEAVIATE_API_KEY) - - use_embed: bool - Whether to use the client embedding options - (Env var WEAVIATE_USE_EMBED) + Args: + url: HttpsUrl | None - Weaviate URL (Env var WEAVIATE_URL) + api_key: SecretStr | None - Weaviate token (Env var WEAVIATE_API_KEY) + use_embed: bool - Whether to use the client embedding options + (Env var WEAVIATE_USE_EMBED) """ url: HttpsUrl | None = None @@ -23,8 +23,11 @@ class WeaviateSettings(BaseModelSettings): use_embed: bool = False class Config(BaseModelSettings.Config): + """Configuration for the Weaviate model settings.""" + env_prefix = "WEAVIATE_" def validate_settings(self): + """Validate the Weaviate settings.""" if not self.use_embed and not self.url: raise ValueError("Weaviate config must have either url or use_embed set") diff --git a/python/semantic_kernel/connectors/openai_plugin/openai_utils.py b/python/semantic_kernel/connectors/openai_plugin/openai_utils.py index 0776d97d9859..44ce20f127ce 100644 --- a/python/semantic_kernel/connectors/openai_plugin/openai_utils.py +++ b/python/semantic_kernel/connectors/openai_plugin/openai_utils.py @@ -15,7 +15,6 @@ class OpenAIUtils: @staticmethod def parse_openai_manifest_for_openapi_spec_url(plugin_json: dict[str, Any]) -> str: """Extract the OpenAPI Spec URL from the plugin JSON.""" - try: api_type = plugin_json["api"]["type"] except KeyError as ex: diff --git a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation.py b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation.py index 60c2e4d6bdde..17d206f6ffcb 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation.py +++ b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation.py @@ -57,6 +57,7 @@ def __init__( request_body: "RestApiOperationPayload | None" = None, responses: dict[str, "RestApiOperationExpectedResponse"] | None = None, ): + """Initialize the RestApiOperation.""" self.id = id self.method = method.upper() self.server_url = server_url @@ -78,6 +79,7 @@ def url_join(self, base_url: str, path: str): return urlunparse(parsed_base._replace(path=full_path)) def build_headers(self, arguments: dict[str, Any]) -> dict[str, str]: + """Build the headers for the operation.""" headers = {} parameters = [p for p in self.parameters if p.location == RestApiOperationParameterLocation.HEADER] @@ -98,11 +100,13 @@ def build_headers(self, arguments: dict[str, Any]) -> dict[str, str]: return headers def build_operation_url(self, arguments, server_url_override=None, api_host_url=None): + """Build the URL for the operation.""" server_url = self.get_server_url(server_url_override, api_host_url) path = self.build_path(self.path, arguments) return urljoin(server_url.geturl(), path.lstrip("/")) def get_server_url(self, server_url_override=None, api_host_url=None): + """Get the server URL for the operation.""" if server_url_override is not None and server_url_override.geturl() != b"": server_url_string = server_url_override.geturl() else: @@ -119,6 +123,7 @@ def get_server_url(self, server_url_override=None, api_host_url=None): return urlparse(server_url_string) def build_path(self, path_template: str, arguments: dict[str, Any]) -> str: + """Build the path for the operation.""" parameters = [p for p in self.parameters if p.location == RestApiOperationParameterLocation.PATH] for parameter in parameters: argument = arguments.get(parameter.name) @@ -133,6 +138,7 @@ def build_path(self, path_template: str, arguments: dict[str, Any]) -> str: return path_template def build_query_string(self, arguments: dict[str, Any]) -> str: + """Build the query string for the operation.""" segments = [] parameters = [p for p in self.parameters if p.location == RestApiOperationParameterLocation.QUERY] for parameter in parameters: @@ -148,6 +154,7 @@ def build_query_string(self, arguments: dict[str, Any]) -> str: return urlencode(segments) def replace_invalid_symbols(self, parameter_name): + """Replace invalid symbols in the parameter name with underscores.""" return RestApiOperation.INVALID_SYMBOLS_REGEX.sub("_", parameter_name) def get_parameters( @@ -156,6 +163,7 @@ def get_parameters( add_payload_params_from_metadata: bool = True, enable_payload_spacing: bool = False, ) -> list["RestApiOperationParameter"]: + """Get the parameters for the operation.""" params = list(operation.parameters) if operation.request_body is not None: params.extend( @@ -172,6 +180,7 @@ def get_parameters( return params def create_payload_artificial_parameter(self, operation: "RestApiOperation") -> "RestApiOperationParameter": + """Create an artificial parameter for the REST API request body.""" return RestApiOperationParameter( name=self.PAYLOAD_ARGUMENT_NAME, type=( @@ -188,6 +197,7 @@ def create_payload_artificial_parameter(self, operation: "RestApiOperation") -> ) def create_content_type_artificial_parameter(self) -> "RestApiOperationParameter": + """Create an artificial parameter for the content type of the REST API request body.""" return RestApiOperationParameter( name=self.CONTENT_TYPE_ARGUMENT_NAME, type="string", @@ -233,6 +243,7 @@ def _get_parameters_from_payload_metadata( def get_payload_parameters( self, operation: "RestApiOperation", use_parameters_from_metadata: bool, enable_namespacing: bool ): + """Get the payload parameters for the operation.""" if use_parameters_from_metadata: if operation.request_body is None: raise Exception( @@ -252,13 +263,17 @@ def get_payload_parameters( def get_default_response( self, responses: dict[str, RestApiOperationExpectedResponse], preferred_responses: list[str] ) -> RestApiOperationExpectedResponse | None: + """Get the default response for the operation. + + If no appropriate response is found, returns None. + """ for code in preferred_responses: if code in responses: return responses[code] - # If no appropriate response is found, return None return None def get_default_return_parameter(self, preferred_responses: list[str] | None = None) -> KernelParameterMetadata: + """Get the default return parameter for the operation.""" if preferred_responses is None: preferred_responses = self._preferred_responses diff --git a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_expected_response.py b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_expected_response.py index 33240b927fbe..2cc251cbe048 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_expected_response.py +++ b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_expected_response.py @@ -7,6 +7,7 @@ @experimental_class class RestApiOperationExpectedResponse: def __init__(self, description: str, media_type: str, schema: str | None = None): + """Initialize the RestApiOperationExpectedResponse.""" self.description = description self.media_type = media_type self.schema = schema diff --git a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_parameter.py b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_parameter.py index fc4d2ff843d7..c74a10acac34 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_parameter.py +++ b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_parameter.py @@ -29,6 +29,7 @@ def __init__( schema: str | None = None, response: RestApiOperationExpectedResponse | None = None, ): + """Initialize the RestApiOperationParameter.""" self.name = name self.type = type self.location = location diff --git a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_payload.py b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_payload.py index aae370e6f342..ad102911f665 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_payload.py +++ b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_payload.py @@ -15,6 +15,7 @@ def __init__( description: str | None = None, schema: str | None = None, ): + """Initialize the RestApiOperationPayload.""" self.media_type = media_type self.properties = properties self.description = description diff --git a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_payload_property.py b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_payload_property.py index d1b81c272baf..cf6fed327184 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_payload_property.py +++ b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_payload_property.py @@ -17,6 +17,7 @@ def __init__( default_value: Any | None = None, schema: str | None = None, ): + """Initialize the RestApiOperationPayloadProperty.""" self.name = name self.type = type self.properties = properties diff --git a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_run_options.py b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_run_options.py index eaa5a952c7d5..efc7d7434948 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_run_options.py +++ b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_operation_run_options.py @@ -8,5 +8,6 @@ class RestApiOperationRunOptions: """The options for running the REST API operation.""" def __init__(self, server_url_override=None, api_host_url=None): + """Initialize the REST API operation run options.""" self.server_url_override: str = server_url_override self.api_host_url: str = api_host_url diff --git a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_uri.py b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_uri.py index c85a8113795e..16219521870e 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_uri.py +++ b/python/semantic_kernel/connectors/openapi_plugin/models/rest_api_uri.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. - from urllib.parse import urlparse from semantic_kernel.utils.experimental_decorator import experimental_class @@ -11,8 +10,10 @@ class Uri: """The Uri class that represents the URI.""" def __init__(self, uri): + """Initialize the Uri.""" self.uri = uri def get_left_part(self): + """Get the left part of the URI.""" parsed_uri = urlparse(self.uri) return f"{parsed_uri.scheme}://{parsed_uri.netloc}" diff --git a/python/semantic_kernel/connectors/openapi_plugin/openapi_function_execution_parameters.py b/python/semantic_kernel/connectors/openapi_plugin/openapi_function_execution_parameters.py index bec045180ab6..1468e200ab45 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/openapi_function_execution_parameters.py +++ b/python/semantic_kernel/connectors/openapi_plugin/openapi_function_execution_parameters.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. - from collections.abc import Awaitable, Callable from typing import Any from urllib.parse import urlparse @@ -28,6 +27,7 @@ class OpenAPIFunctionExecutionParameters(KernelBaseModel): operations_to_exclude: list[str] = Field(default_factory=list) def model_post_init(self, __context: Any) -> None: + """Post initialization method for the model.""" from semantic_kernel.connectors.telemetry import HTTP_USER_AGENT if self.server_url_override: diff --git a/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py b/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py index b22585d92700..127702997777 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py +++ b/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py @@ -2,7 +2,8 @@ import logging from collections import OrderedDict -from typing import TYPE_CHECKING, Any, Generator +from collections.abc import Generator +from typing import TYPE_CHECKING, Any from urllib.parse import urlparse from prance import ResolvingParser @@ -35,8 +36,7 @@ @experimental_class class OpenApiParser: - """ - NOTE: SK Python only supports the OpenAPI Spec >=3.0 + """NOTE: SK Python only supports the OpenAPI Spec >=3.0. Import an OpenAPI file. diff --git a/python/semantic_kernel/connectors/openapi_plugin/openapi_runner.py b/python/semantic_kernel/connectors/openapi_plugin/openapi_runner.py index a0478bcb0161..a6b74df0b6b6 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/openapi_runner.py +++ b/python/semantic_kernel/connectors/openapi_plugin/openapi_runner.py @@ -4,7 +4,7 @@ import logging from collections import OrderedDict from collections.abc import Callable, Mapping -from typing import TYPE_CHECKING, Any +from typing import Any from urllib.parse import urlparse, urlunparse import httpx @@ -21,15 +21,12 @@ from semantic_kernel.functions.kernel_arguments import KernelArguments from semantic_kernel.utils.experimental_decorator import experimental_class -if TYPE_CHECKING: - pass - logger: logging.Logger = logging.getLogger(__name__) @experimental_class class OpenApiRunner: - """The OpenApiRunner that runs the operations defined in the OpenAPI manifest""" + """The OpenApiRunner that runs the operations defined in the OpenAPI manifest.""" payload_argument_name = "payload" media_type_application_json = "application/json" @@ -42,6 +39,7 @@ def __init__( enable_dynamic_payload: bool = True, enable_payload_namespacing: bool = False, ): + """Initialize the OpenApiRunner.""" self.spec = Spec.from_dict(parsed_openapi_document) self.auth_callback = auth_callback self.http_client = http_client @@ -102,11 +100,13 @@ def build_json_object(self, properties, arguments, property_namespace=None): return result def build_operation_payload(self, operation: RestApiOperation, arguments: KernelArguments) -> tuple[str, str]: + """Build the operation payload.""" if operation.request_body is None and self.payload_argument_name not in arguments: return None, None return self.build_json_payload(operation.request_body, arguments) def get_argument_name_for_payload(self, property_name, property_namespace=None): + """Get argument name for the payload.""" if not self.enable_payload_namespacing: return property_name return f"{property_namespace}.{property_name}" if property_namespace else property_name @@ -123,6 +123,7 @@ async def run_operation( arguments: KernelArguments | None = None, options: RestApiOperationRunOptions | None = None, ) -> str: + """Run the operation.""" from semantic_kernel.connectors.telemetry import HTTP_USER_AGENT url = self.build_operation_url( diff --git a/python/semantic_kernel/connectors/search_engine/bing_connector.py b/python/semantic_kernel/connectors/search_engine/bing_connector.py index 7917378129e6..8b822d7d03f6 100644 --- a/python/semantic_kernel/connectors/search_engine/bing_connector.py +++ b/python/semantic_kernel/connectors/search_engine/bing_connector.py @@ -14,19 +14,17 @@ class BingConnector(ConnectorBase): - """ - A search engine connector that uses the Bing Search API to perform a web search - """ + """A search engine connector that uses the Bing Search API to perform a web search.""" _api_key: str def __init__(self, api_key: str | None = None, env_file_path: str | None = None) -> None: """Initializes a new instance of the BingConnector class. - Arguments: - api_key {str | None}: The Bing Search API key. If provided, will override + Args: + api_key (str | None): The Bing Search API key. If provided, will override the value in the env vars or .env file. - env_file_path {str | None}: The optional path to the .env file. If provided, + env_file_path (str | None): The optional path to the .env file. If provided, the settings are read from this file path location. """ bing_settings = None @@ -38,18 +36,11 @@ def __init__(self, api_key: str | None = None, env_file_path: str | None = None) self._api_key = api_key or ( bing_settings.api_key.get_secret_value() if bing_settings and bing_settings.api_key else None ) - assert self._api_key, "API key cannot be 'None' or empty." + if not self._api_key: + raise ValueError("API key cannot be 'None' or empty.") async def search(self, query: str, num_results: int = 1, offset: int = 0) -> list[str]: - """ - Returns the search results of the query provided by pinging the Bing web search API. - Returns `num_results` results and ignores the first `offset`. - - :param query: search query - :param num_results: the number of search results to return - :param offset: the number of search results to ignore - :return: list of search results - """ + """Returns the search results of the query provided by pinging the Bing web search API.""" if not query: raise ServiceInvalidRequestError("query cannot be 'None' or empty.") diff --git a/python/semantic_kernel/connectors/search_engine/bing_connector_settings.py b/python/semantic_kernel/connectors/search_engine/bing_connector_settings.py index 38a4966d505d..f4639d2c7572 100644 --- a/python/semantic_kernel/connectors/search_engine/bing_connector_settings.py +++ b/python/semantic_kernel/connectors/search_engine/bing_connector_settings.py @@ -5,7 +5,7 @@ class BingSettings(BaseSettings): - """Bing Connector settings + """Bing Connector settings. The settings are first loaded from environment variables with the prefix 'BING_'. If the environment variables are not found, the settings can be loaded from a .env file with the @@ -21,6 +21,8 @@ class BingSettings(BaseSettings): api_key: SecretStr | None = None class Config: + """Configuration for the Bing Connector settings.""" + env_prefix = "BING_" env_file = None env_file_encoding = "utf-8" @@ -29,6 +31,7 @@ class Config: @classmethod def create(cls, **kwargs): + """Create an instance of the Bing Connector settings.""" if "env_file_path" in kwargs and kwargs["env_file_path"]: cls.Config.env_file = kwargs["env_file_path"] else: diff --git a/python/semantic_kernel/connectors/search_engine/connector.py b/python/semantic_kernel/connectors/search_engine/connector.py index 3a27824d9b33..0ee9593afbfb 100644 --- a/python/semantic_kernel/connectors/search_engine/connector.py +++ b/python/semantic_kernel/connectors/search_engine/connector.py @@ -4,10 +4,9 @@ class ConnectorBase(ABC): - """ - Base class for search engine connectors - """ + """Base class for search engine connectors.""" @abstractmethod async def search(self, query: str, num_results: int = 1, offset: int = 0) -> list[str]: + """Returns the search results of the query provided by pinging the search engine API.""" pass diff --git a/python/semantic_kernel/connectors/search_engine/google_connector.py b/python/semantic_kernel/connectors/search_engine/google_connector.py index 956e00598b5e..6185ac35be69 100644 --- a/python/semantic_kernel/connectors/search_engine/google_connector.py +++ b/python/semantic_kernel/connectors/search_engine/google_connector.py @@ -12,14 +12,13 @@ class GoogleConnector(ConnectorBase): - """ - A search engine connector that uses the Google Custom Search API to perform a web search. - """ + """A search engine connector that uses the Google Custom Search API to perform a web search.""" _api_key: str _search_engine_id: str def __init__(self, api_key: str, search_engine_id: str) -> None: + """Initializes a new instance of the GoogleConnector class.""" self._api_key = api_key self._search_engine_id = search_engine_id @@ -30,15 +29,7 @@ def __init__(self, api_key: str, search_engine_id: str) -> None: raise ServiceInitializationError("Google search engine ID cannot be null.") async def search(self, query: str, num_results: int = 1, offset: int = 0) -> list[str]: - """ - Returns the search results of the query provided by pinging the Google Custom search API. - Returns `num_results` results and ignores the first `offset`. - - :param query: search query - :param num_results: the number of search results to return - :param offset: the number of search results to ignore - :return: list of search results - """ + """Returns the search results of the query provided by pinging the Google Custom search API.""" if not query: raise ServiceInvalidRequestError("query cannot be 'None' or empty.") diff --git a/python/semantic_kernel/connectors/telemetry.py b/python/semantic_kernel/connectors/telemetry.py index c91d72c5b69b..6a788681ad5c 100644 --- a/python/semantic_kernel/connectors/telemetry.py +++ b/python/semantic_kernel/connectors/telemetry.py @@ -27,8 +27,7 @@ def prepend_semantic_kernel_to_user_agent(headers: dict[str, Any]): - """ - Prepend "Semantic-Kernel" to the User-Agent in the headers. + """Prepend "Semantic-Kernel" to the User-Agent in the headers. Args: headers: The existing headers dictionary. @@ -36,7 +35,6 @@ def prepend_semantic_kernel_to_user_agent(headers: dict[str, Any]): Returns: The modified headers dictionary with "Semantic-Kernel" prepended to the User-Agent. """ - headers[USER_AGENT] = f"{HTTP_USER_AGENT} {headers[USER_AGENT]}" if USER_AGENT in headers else f"{HTTP_USER_AGENT}" return headers diff --git a/python/semantic_kernel/connectors/utils/document_loader.py b/python/semantic_kernel/connectors/utils/document_loader.py index f5e6c23bb6d8..616ea6d83b46 100644 --- a/python/semantic_kernel/connectors/utils/document_loader.py +++ b/python/semantic_kernel/connectors/utils/document_loader.py @@ -20,7 +20,7 @@ async def from_uri( auth_callback: Callable[[Any], None] | None, user_agent: str | None = HTTP_USER_AGENT, ): - """Load the manifest from the given URL""" + """Load the manifest from the given URL.""" headers = {"User-Agent": user_agent} async with http_client as client: if auth_callback: diff --git a/python/semantic_kernel/contents/author_role.py b/python/semantic_kernel/contents/author_role.py index 7f5df3f8b267..a840d8a358cb 100644 --- a/python/semantic_kernel/contents/author_role.py +++ b/python/semantic_kernel/contents/author_role.py @@ -3,7 +3,7 @@ class AuthorRole(str, Enum): - """Author role enum""" + """Author role enum.""" SYSTEM = "system" USER = "user" diff --git a/python/semantic_kernel/contents/chat_history.py b/python/semantic_kernel/contents/chat_history.py index 462c58162b69..47189b1df092 100644 --- a/python/semantic_kernel/contents/chat_history.py +++ b/python/semantic_kernel/contents/chat_history.py @@ -5,7 +5,7 @@ from functools import singledispatchmethod from html import unescape from typing import Any -from xml.etree.ElementTree import Element, tostring +from xml.etree.ElementTree import Element, tostring # nosec from defusedxml.ElementTree import XML, ParseError from pydantic import field_validator @@ -21,8 +21,7 @@ class ChatHistory(KernelBaseModel): - """ - This class holds the history of chat messages from a chat conversation. + """This class holds the history of chat messages from a chat conversation. Note: the constructor takes a system_message parameter, which is not part of the class definition. This is to allow the system_message to be passed in @@ -35,9 +34,9 @@ class ChatHistory(KernelBaseModel): messages: list[ChatMessageContent] def __init__(self, **data: Any): - """ - Initializes a new instance of the ChatHistory class, optionally incorporating a message and/or - a system message at the beginning of the chat history. + """Initializes a new instance of the ChatHistory class. + + Optionally incorporating a message and/or a system message at the beginning of the chat history. This constructor allows for flexible initialization with chat messages and an optional messages or a system message. If both 'messages' (a list of ChatMessageContent instances) and 'system_message' are @@ -46,15 +45,17 @@ def __init__(self, **data: Any): initialized with the 'system_message' as its first item. If 'messages' are provided without a 'system_message', the chat history is initialized with the provided messages as is. - Parameters: - - **data: Arbitrary keyword arguments. The constructor looks for two optional keys: - - 'messages': Optional[List[ChatMessageContent]], a list of chat messages to include in the history. - - 'system_message' Optional[str]: An optional string representing a system-generated message to be - included at the start of the chat history. - Note: The 'system_message' is not retained as part of the class's attributes; it's used during initialization and then discarded. The rest of the keyword arguments are passed to the superclass constructor and handled according to the Pydantic model's behavior. + + Args: + **data: Arbitrary keyword arguments. + The constructor looks for two optional keys: + - 'messages': Optional[List[ChatMessageContent]], a list of chat messages to include in the history. + - 'system_message' Optional[str]: An optional string representing a system-generated message to be + included at the start of the chat history. + """ system_message_content = data.pop("system_message", None) @@ -89,10 +90,12 @@ def add_system_message(self, content: str | list[KernelContent], **kwargs) -> No @add_system_message.register def add_system_message_str(self, content: str, **kwargs: Any) -> None: + """Add a system message to the chat history.""" self.add_message(message=self._prepare_for_add(role=AuthorRole.SYSTEM, content=content, **kwargs)) @add_system_message.register(list) def add_system_message_list(self, content: list[KernelContent], **kwargs: Any) -> None: + """Add a system message to the chat history.""" self.add_message(message=self._prepare_for_add(role=AuthorRole.SYSTEM, items=content, **kwargs)) @singledispatchmethod @@ -102,10 +105,12 @@ def add_user_message(self, content: str | list[KernelContent], **kwargs: Any) -> @add_user_message.register def add_user_message_str(self, content: str, **kwargs: Any) -> None: + """Add a user message to the chat history.""" self.add_message(message=self._prepare_for_add(role=AuthorRole.USER, content=content, **kwargs)) @add_user_message.register(list) def add_user_message_list(self, content: list[KernelContent], **kwargs: Any) -> None: + """Add a user message to the chat history.""" self.add_message(message=self._prepare_for_add(role=AuthorRole.USER, items=content, **kwargs)) @singledispatchmethod @@ -115,10 +120,12 @@ def add_assistant_message(self, content: str | list[KernelContent], **kwargs: An @add_assistant_message.register def add_assistant_message_str(self, content: str, **kwargs: Any) -> None: + """Add an assistant message to the chat history.""" self.add_message(message=self._prepare_for_add(role=AuthorRole.ASSISTANT, content=content, **kwargs)) @add_assistant_message.register(list) def add_assistant_message_list(self, content: list[KernelContent], **kwargs: Any) -> None: + """Add an assistant message to the chat history.""" self.add_message(message=self._prepare_for_add(role=AuthorRole.ASSISTANT, items=content, **kwargs)) @singledispatchmethod @@ -128,10 +135,12 @@ def add_tool_message(self, content: str | list[KernelContent], **kwargs: Any) -> @add_tool_message.register def add_tool_message_str(self, content: str, **kwargs: Any) -> None: + """Add a tool message to the chat history.""" self.add_message(message=self._prepare_for_add(role=AuthorRole.TOOL, content=content, **kwargs)) @add_tool_message.register(list) def add_tool_message_list(self, content: list[KernelContent], **kwargs: Any) -> None: + """Add a tool message to the chat history.""" self.add_message(message=self._prepare_for_add(role=AuthorRole.TOOL, items=content, **kwargs)) def add_message( @@ -241,8 +250,7 @@ def __eq__(self, other: Any) -> bool: @classmethod def from_rendered_prompt(cls, rendered_prompt: str) -> "ChatHistory": - """ - Create a ChatHistory instance from a rendered prompt. + """Create a ChatHistory instance from a rendered prompt. Args: rendered_prompt (str): The rendered prompt to convert to a ChatHistory instance. @@ -273,8 +281,7 @@ def from_rendered_prompt(cls, rendered_prompt: str) -> "ChatHistory": return cls(messages=messages) def serialize(self) -> str: - """ - Serializes the ChatHistory instance to a JSON string. + """Serializes the ChatHistory instance to a JSON string. Returns: str: A JSON string representation of the ChatHistory instance. @@ -289,8 +296,7 @@ def serialize(self) -> str: @classmethod def restore_chat_history(cls, chat_history_json: str) -> "ChatHistory": - """ - Restores a ChatHistory instance from a JSON string. + """Restores a ChatHistory instance from a JSON string. Args: chat_history_json (str): The JSON string to deserialize @@ -309,8 +315,7 @@ def restore_chat_history(cls, chat_history_json: str) -> "ChatHistory": raise ContentInitializationError(f"Invalid JSON format: {e}") def store_chat_history_to_file(self, file_path: str) -> None: - """ - Stores the serialized ChatHistory to a file. + """Stores the serialized ChatHistory to a file. Args: file_path (str): The path to the file where the serialized data will be stored. @@ -321,8 +326,7 @@ def store_chat_history_to_file(self, file_path: str) -> None: @classmethod def load_chat_history_from_file(cls, file_path: str) -> "ChatHistory": - """ - Loads the ChatHistory from a file. + """Loads the ChatHistory from a file. Args: file_path (str): The path to the file from which to load the ChatHistory. diff --git a/python/semantic_kernel/contents/chat_message_content.py b/python/semantic_kernel/contents/chat_message_content.py index 46acdf7bc1c7..b4c2dbe277ea 100644 --- a/python/semantic_kernel/contents/chat_message_content.py +++ b/python/semantic_kernel/contents/chat_message_content.py @@ -4,7 +4,7 @@ from enum import Enum from html import unescape from typing import Any, Union, overload -from xml.etree.ElementTree import Element +from xml.etree.ElementTree import Element # nosec from defusedxml import ElementTree from pydantic import Field @@ -72,20 +72,7 @@ def __init__( ai_model_id: str | None = None, metadata: dict[str, Any] | None = None, **kwargs: Any, - ) -> None: - """All Chat Completion Services should return an instance of this class as response. - Or they can implement their own subclass of this class and return an instance. - - Args: - inner_content: Optional[Any] - The inner content of the response, - this should hold all the information from the response so even - when not creating a subclass a developer can leverage the full thing. - ai_model_id: Optional[str] - The id of the AI model that generated this response. - metadata: Dict[str, Any] - Any metadata that should be attached to the response. - role: ChatRole - The role of the chat message. - items: list[TextContent, StreamingTextContent, FunctionCallContent, FunctionResultContent] - The content. - encoding: Optional[str] - The encoding of the text. - """ + ) -> None: ... @overload def __init__( @@ -99,20 +86,7 @@ def __init__( ai_model_id: str | None = None, metadata: dict[str, Any] | None = None, **kwargs: Any, - ) -> None: - """All Chat Completion Services should return an instance of this class as response. - Or they can implement their own subclass of this class and return an instance. - - Args: - inner_content: Optional[Any] - The inner content of the response, - this should hold all the information from the response so even - when not creating a subclass a developer can leverage the full thing. - ai_model_id: Optional[str] - The id of the AI model that generated this response. - metadata: Dict[str, Any] - Any metadata that should be attached to the response. - role: ChatRole - The role of the chat message. - content: str - The text of the response. - encoding: Optional[str] - The encoding of the text. - """ + ) -> None: ... def __init__( # type: ignore self, @@ -127,19 +101,21 @@ def __init__( # type: ignore metadata: dict[str, Any] | None = None, **kwargs: Any, ): - """All Chat Completion Services should return an instance of this class as response. - Or they can implement their own subclass of this class and return an instance. + """Create a ChatMessageContent instance. Args: + role: ChatRole - The role of the chat message. + items: list[TextContent, StreamingTextContent, FunctionCallContent, FunctionResultContent] - The content. + content: str - The text of the response. inner_content: Optional[Any] - The inner content of the response, this should hold all the information from the response so even when not creating a subclass a developer can leverage the full thing. + name: Optional[str] - The name of the response. + encoding: Optional[str] - The encoding of the text. + finish_reason: Optional[FinishReason] - The reason the response was finished. ai_model_id: Optional[str] - The id of the AI model that generated this response. metadata: Dict[str, Any] - Any metadata that should be attached to the response. - role: ChatRole - The role of the chat message. - content: str - The text of the response. - items: list[TextContent, StreamingTextContent, FunctionCallContent, FunctionResultContent] - The content. - encoding: Optional[str] - The encoding of the text. + **kwargs: Any - Any additional fields to set on the instance. """ kwargs["role"] = role if encoding: @@ -271,7 +247,6 @@ def to_prompt(self) -> str: Returns: str - The prompt from the ChatMessageContent. """ - root = self.to_element() return ElementTree.tostring(root, encoding=self.encoding or "unicode", short_empty_elements=False) @@ -289,7 +264,7 @@ def to_dict(self, role_key: str = "role", content_key: str = "content") -> dict[ else: ret[content_key] = self._parse_items() if self.role == AuthorRole.TOOL: - assert isinstance(self.items[0], FunctionResultContent) + assert isinstance(self.items[0], FunctionResultContent) # nosec ret["tool_call_id"] = self.items[0].id or "" if self.role != AuthorRole.TOOL and self.name: ret["name"] = self.name diff --git a/python/semantic_kernel/contents/finish_reason.py b/python/semantic_kernel/contents/finish_reason.py index bc1292d7e079..b9862c4bd683 100644 --- a/python/semantic_kernel/contents/finish_reason.py +++ b/python/semantic_kernel/contents/finish_reason.py @@ -3,7 +3,7 @@ class FinishReason(str, Enum): - """Finish Reason enum""" + """Finish Reason enum.""" STOP = "stop" LENGTH = "length" diff --git a/python/semantic_kernel/contents/function_call_content.py b/python/semantic_kernel/contents/function_call_content.py index b6bd0aee42cd..d761d54a97d8 100644 --- a/python/semantic_kernel/contents/function_call_content.py +++ b/python/semantic_kernel/contents/function_call_content.py @@ -4,7 +4,7 @@ import logging from functools import cached_property from typing import TYPE_CHECKING, Any -from xml.etree.ElementTree import Element +from xml.etree.ElementTree import Element # nosec from semantic_kernel.contents.const import FUNCTION_CALL_CONTENT_TAG from semantic_kernel.contents.kernel_content import KernelContent @@ -35,6 +35,7 @@ def plugin_name(self) -> str | None: return self.split_name()[0] def __str__(self) -> str: + """Return the function call as a string.""" return f"{self.name}({self.arguments})" def __add__(self, other: "FunctionCallContent | None") -> "FunctionCallContent": diff --git a/python/semantic_kernel/contents/function_result_content.py b/python/semantic_kernel/contents/function_result_content.py index 9a2bda7a9ed8..e9d28461ff72 100644 --- a/python/semantic_kernel/contents/function_result_content.py +++ b/python/semantic_kernel/contents/function_result_content.py @@ -2,7 +2,7 @@ from functools import cached_property from typing import TYPE_CHECKING, Any -from xml.etree.ElementTree import Element +from xml.etree.ElementTree import Element # nosec from pydantic import field_validator @@ -62,6 +62,7 @@ def _validate_result(cls, result: Any): return result def __str__(self) -> str: + """Return the text of the response.""" return self.result def to_element(self) -> Element: diff --git a/python/semantic_kernel/contents/kernel_content.py b/python/semantic_kernel/contents/kernel_content.py index 07470d40942f..a03b474409ea 100644 --- a/python/semantic_kernel/contents/kernel_content.py +++ b/python/semantic_kernel/contents/kernel_content.py @@ -17,17 +17,21 @@ class KernelContent(KernelBaseModel, ABC): @abstractmethod def __str__(self) -> str: + """Return the string representation of the content.""" pass @abstractmethod def to_element(self) -> Any: + """Convert the instance to an Element.""" pass @classmethod @abstractmethod def from_element(cls, element: Any) -> "KernelContent": + """Create an instance from an Element.""" pass @abstractmethod def to_dict(self) -> dict[str, Any]: + """Convert the instance to a dictionary.""" pass diff --git a/python/semantic_kernel/contents/streaming_chat_message_content.py b/python/semantic_kernel/contents/streaming_chat_message_content.py index a6f6c9be1429..7c39be8545c5 100644 --- a/python/semantic_kernel/contents/streaming_chat_message_content.py +++ b/python/semantic_kernel/contents/streaming_chat_message_content.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Any, Union, overload -from xml.etree.ElementTree import Element +from xml.etree.ElementTree import Element # nosec from semantic_kernel.contents.author_role import AuthorRole from semantic_kernel.contents.chat_message_content import ChatMessageContent @@ -54,20 +54,7 @@ def __init__( finish_reason: FinishReason | None = None, ai_model_id: str | None = None, metadata: dict[str, Any] | None = None, - ) -> None: - """All Chat Completion Services should return an instance of this class as response for streaming. - Or they can implement their own subclass of this class and return an instance. - - Args: - inner_content: Optional[Any] - The inner content of the response, - this should hold all the information from the response so even - when not creating a subclass a developer can leverage the full thing. - ai_model_id: Optional[str] - The id of the AI model that generated this response. - metadata: Dict[str, Any] - Any metadata that should be attached to the response. - role: ChatRole - The role of the chat message. - items: list[TextContent, FunctionCallContent, FunctionResultContent] - The content. - encoding: Optional[str] - The encoding of the text. - """ + ) -> None: ... @overload def __init__( @@ -81,20 +68,7 @@ def __init__( finish_reason: FinishReason | None = None, ai_model_id: str | None = None, metadata: dict[str, Any] | None = None, - ) -> None: - """All Chat Completion Services should return an instance of this class as response for streaming. - Or they can implement their own subclass of this class and return an instance. - - Args: - inner_content: Optional[Any] - The inner content of the response, - this should hold all the information from the response so even - when not creating a subclass a developer can leverage the full thing. - ai_model_id: Optional[str] - The id of the AI model that generated this response. - metadata: Dict[str, Any] - Any metadata that should be attached to the response. - role: ChatRole - The role of the chat message. - content: str - The text of the response. - encoding: Optional[str] - The encoding of the text. - """ + ) -> None: ... def __init__( # type: ignore self, @@ -109,19 +83,21 @@ def __init__( # type: ignore ai_model_id: str | None = None, metadata: dict[str, Any] | None = None, ): - """All Chat Completion Services should return an instance of this class as response for streaming. - Or they can implement their own subclass of this class and return an instance. + """Create a new instance of StreamingChatMessageContent. Args: + role: ChatRole - The role of the chat message. + choice_index: int - The index of the choice that generated this response. + items: list[TextContent, FunctionCallContent, FunctionResultContent] - The content. + content: str - The text of the response. inner_content: Optional[Any] - The inner content of the response, this should hold all the information from the response so even when not creating a subclass a developer can leverage the full thing. - ai_model_id: Optional[str] - The id of the AI model that generated this response. - metadata: Dict[str, Any] - Any metadata that should be attached to the response. - role: ChatRole - The role of the chat message. - content: str - The text of the response. - items: list[TextContent, FunctionCallContent, FunctionResultContent] - The content. + name: Optional[str] - The name of the response. encoding: Optional[str] - The encoding of the text. + finish_reason: Optional[FinishReason] - The reason the response was finished. + metadata: Dict[str, Any] - Any metadata that should be attached to the response. + ai_model_id: Optional[str] - The id of the AI model that generated this response. """ kwargs: dict[str, Any] = { "role": role, diff --git a/python/semantic_kernel/contents/streaming_content_mixin.py b/python/semantic_kernel/contents/streaming_content_mixin.py index 001ea8ddbb24..065b03f8fffd 100644 --- a/python/semantic_kernel/contents/streaming_content_mixin.py +++ b/python/semantic_kernel/contents/streaming_content_mixin.py @@ -9,7 +9,6 @@ else: from typing_extensions import Self - from semantic_kernel.kernel_pydantic import KernelBaseModel @@ -20,8 +19,10 @@ class StreamingContentMixin(KernelBaseModel, ABC): @abstractmethod def __bytes__(self) -> bytes: + """Return the content of the response encoded in the encoding.""" pass @abstractmethod def __add__(self, other: Any) -> Self: + """Combine two streaming contents together.""" pass diff --git a/python/semantic_kernel/contents/streaming_text_content.py b/python/semantic_kernel/contents/streaming_text_content.py index da3ea800860e..5e33a4e3f330 100644 --- a/python/semantic_kernel/contents/streaming_text_content.py +++ b/python/semantic_kernel/contents/streaming_text_content.py @@ -28,6 +28,7 @@ class StreamingTextContent(StreamingContentMixin, TextContent): """ def __bytes__(self) -> bytes: + """Return the content of the response encoded in the encoding.""" return self.text.encode(self.encoding if self.encoding else "utf-8") if self.text else b"" def __add__(self, other: "TextContent") -> "StreamingTextContent": diff --git a/python/semantic_kernel/contents/text_content.py b/python/semantic_kernel/contents/text_content.py index 8d110ec50686..ddf64696c6a5 100644 --- a/python/semantic_kernel/contents/text_content.py +++ b/python/semantic_kernel/contents/text_content.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. from html import unescape -from xml.etree.ElementTree import Element +from xml.etree.ElementTree import Element # nosec from semantic_kernel.contents.const import TEXT_CONTENT_TAG from semantic_kernel.contents.kernel_content import KernelContent @@ -30,6 +30,7 @@ class TextContent(KernelContent): encoding: str | None = None def __str__(self) -> str: + """Return the text of the response.""" return self.text def to_element(self) -> Element: diff --git a/python/semantic_kernel/core_plugins/conversation_summary_plugin.py b/python/semantic_kernel/core_plugins/conversation_summary_plugin.py index 546102895fe5..081dee2571e4 100644 --- a/python/semantic_kernel/core_plugins/conversation_summary_plugin.py +++ b/python/semantic_kernel/core_plugins/conversation_summary_plugin.py @@ -8,9 +8,7 @@ class ConversationSummaryPlugin: - """ - Semantic plugin that enables conversations summarization. - """ + """Semantic plugin that enables conversations summarization.""" from semantic_kernel.functions.kernel_function_decorator import kernel_function @@ -30,8 +28,7 @@ class ConversationSummaryPlugin: def __init__( self, kernel: "Kernel", prompt_template_config: "PromptTemplateConfig", return_key: str = "summary" ) -> None: - """ - Initializes a new instance of the ConversationSummaryPlugin class. + """Initializes a new instance of the ConversationSummaryPlugin class. :param kernel: The kernel instance. :param prompt_template_config: The prompt template configuration. @@ -57,8 +54,7 @@ async def summarize_conversation( ) -> Annotated[ "KernelArguments", "KernelArguments with the summarized conversation result in key self.return_key." ]: - """ - Given a long conversation transcript, summarize the conversation. + """Given a long conversation transcript, summarize the conversation. :param input: A long conversation transcript. :param kernel: The kernel for function execution. diff --git a/python/semantic_kernel/core_plugins/http_plugin.py b/python/semantic_kernel/core_plugins/http_plugin.py index f88471eafb74..ac66d56011e8 100644 --- a/python/semantic_kernel/core_plugins/http_plugin.py +++ b/python/semantic_kernel/core_plugins/http_plugin.py @@ -11,28 +11,26 @@ class HttpPlugin(KernelBaseModel): - """ - A plugin that provides HTTP functionality. + """A plugin that provides HTTP functionality. Usage: kernel.add_plugin(HttpPlugin(), "http") Examples: - {{http.getAsync $url}} {{http.postAsync $url}} {{http.putAsync $url}} {{http.deleteAsync $url}} """ - @kernel_function(description="Makes a GET request to a uri", name="getAsync") - async def get(self, url: Annotated[str, "The URI to send the request to."]) -> str: - """ - Sends an HTTP GET request to the specified URI and returns - the response body as a string. - params: - uri: The URI to send the request to. - returns: + @kernel_function(description="Makes a GET request to a url", name="getAsync") + async def get(self, url: Annotated[str, "The URL to send the request to."]) -> str: + """Sends an HTTP GET request to the specified URI and returns the response body as a string. + + Args: + url: The URL to send the request to. + + Returns: The response body as a string. """ if not url: @@ -48,10 +46,9 @@ async def post( url: Annotated[str, "The URI to send the request to."], body: Annotated[dict[str, Any] | None, "The body of the request"] = {}, ) -> str: - """ - Sends an HTTP POST request to the specified URI and returns - the response body as a string. - params: + """Sends an HTTP POST request to the specified URI and returns the response body as a string. + + Args: url: The URI to send the request to. body: Contains the body of the request returns: @@ -72,12 +69,13 @@ async def put( url: Annotated[str, "The URI to send the request to."], body: Annotated[dict[str, Any] | None, "The body of the request"] = {}, ) -> str: - """ - Sends an HTTP PUT request to the specified URI and returns - the response body as a string. - params: + """Sends an HTTP PUT request to the specified URI and returns the response body as a string. + + Args: url: The URI to send the request to. - returns: + body: Contains the body of the request + + Returns: The response body as a string. """ if not url: @@ -91,12 +89,12 @@ async def put( @kernel_function(description="Makes a DELETE request to a uri", name="deleteAsync") async def delete(self, url: Annotated[str, "The URI to send the request to."]) -> str: - """ - Sends an HTTP DELETE request to the specified URI and returns - the response body as a string. - params: - uri: The URI to send the request to. - returns: + """Sends an HTTP DELETE request to the specified URI and returns the response body as a string. + + Args: + url: The URI to send the request to. + + Returns: The response body as a string. """ if not url: diff --git a/python/semantic_kernel/core_plugins/math_plugin.py b/python/semantic_kernel/core_plugins/math_plugin.py index 28080725d0b3..87c211368904 100644 --- a/python/semantic_kernel/core_plugins/math_plugin.py +++ b/python/semantic_kernel/core_plugins/math_plugin.py @@ -6,8 +6,7 @@ class MathPlugin: - """ - Description: MathPlugin provides a set of functions to make Math calculations. + """Description: MathPlugin provides a set of functions to make Math calculations. Usage: kernel.add_plugin(MathPlugin(), plugin_name="math") @@ -39,8 +38,7 @@ def subtract( input: Annotated[int, "the first number"], amount: Annotated[int, "the number to subtract"], ) -> int: - """ - Returns the difference of numbers provided. + """Returns the difference of numbers provided. :param initial_value_text: Initial value as string to subtract the specified amount :param context: Contains the context to get the numbers from @@ -54,8 +52,7 @@ def subtract( @staticmethod def add_or_subtract(input: int, amount: int, add: bool) -> int: - """ - Helper function to perform addition or subtraction based on the add flag. + """Helper function to perform addition or subtraction based on the add flag. :param initial_value_text: Initial value as string to add or subtract the specified amount :param context: Contains the context to get the numbers from diff --git a/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_plugin.py b/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_plugin.py index 95a92205b9cd..1a8c7414968a 100644 --- a/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_plugin.py +++ b/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_plugin.py @@ -84,7 +84,6 @@ def _validate_endpoint(cls, endpoint: str): async def _ensure_auth_token(self) -> str: """Ensure the auth token is valid.""" - try: auth_token = await self.auth_callback() except Exception as e: @@ -95,13 +94,14 @@ async def _ensure_auth_token(self) -> str: def _sanitize_input(self, code: str) -> str: """Sanitize input to the python REPL. - Remove whitespace, backtick & python (if llm mistakes python console as terminal) + + Remove whitespace, backtick & python (if llm mistakes python console as terminal). + Args: - query: The query to sanitize + code: The query to sanitize Returns: str: The sanitized query """ - # Removes `, whitespace & python from start code = re.sub(r"^(\s|`)*(?i:python)?\s*", "", code) # Removes whitespace & ` from end @@ -120,16 +120,15 @@ def _sanitize_input(self, code: str) -> str: name="execute_code", ) async def execute_code(self, code: Annotated[str, "The valid Python code to execute"]) -> str: - """ - Executes the provided Python code + """Executes the provided Python code. + Args: code (str): The valid Python code to execute Returns: str: The result of the Python code execution in the form of Result, Stdout, and Stderr Raises: - FunctionExecutionException: If the provided code is empty + FunctionExecutionException: If the provided code is empty. """ - if not code: raise FunctionExecutionException("The provided code is empty") @@ -168,14 +167,15 @@ async def upload_file( self, *, data: BufferedReader = None, remote_file_path: str = None, local_file_path: str = None ) -> SessionsRemoteFileMetadata: """Upload a file to the session pool. + Args: data (BufferedReader): The file data to upload. remote_file_path (str): The path to the file in the session. local_file_path (str): The path to the file on the local machine. + Returns: RemoteFileMetadata: The metadata of the uploaded file. """ - if data and local_file_path: raise ValueError("data and local_file_path cannot be provided together") @@ -207,6 +207,7 @@ async def upload_file( @kernel_function(name="list_files", description="Lists all files in the provided Session ID") async def list_files(self) -> list[SessionsRemoteFileMetadata]: """List the files in the session pool. + Returns: list[SessionsRemoteFileMetadata]: The metadata for the files in the session pool """ @@ -228,10 +229,12 @@ async def list_files(self) -> list[SessionsRemoteFileMetadata]: async def download_file(self, *, remote_file_path: str, local_file_path: str = None) -> BufferedReader | None: """Download a file from the session pool. + Args: remote_file_path: The path to download the file from, relative to `/mnt/data`. local_file_path: The path to save the downloaded file to. If not provided, the file is returned as a BufferedReader. + Returns: BufferedReader: The data of the downloaded file. """ diff --git a/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_settings.py b/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_settings.py index df71ffb5adcd..190dc49db190 100644 --- a/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_settings.py +++ b/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_settings.py @@ -46,6 +46,8 @@ class ACASessionsSettings(BaseSettings): pool_management_endpoint: HttpsUrl class Config: + """Configuration for the Azure Container Apps sessions settings.""" + env_prefix = "ACA_" env_file = None env_file_encoding = "utf-8" @@ -54,6 +56,7 @@ class Config: @classmethod def create(cls, **kwargs): + """Create an instance of the Azure Container Apps sessions settings.""" if "env_file_path" in kwargs and kwargs["env_file_path"]: cls.Config.env_file = kwargs["env_file_path"] else: diff --git a/python/semantic_kernel/core_plugins/text_memory_plugin.py b/python/semantic_kernel/core_plugins/text_memory_plugin.py index 1cfd25fc9c9d..64479c3a4f5b 100644 --- a/python/semantic_kernel/core_plugins/text_memory_plugin.py +++ b/python/semantic_kernel/core_plugins/text_memory_plugin.py @@ -23,12 +23,11 @@ class TextMemoryPlugin(KernelBaseModel): embeddings_kwargs: dict[str, Any] = Field(default_factory=dict) def __init__(self, memory: SemanticTextMemoryBase, embeddings_kwargs: dict[str, Any] = {}) -> None: - """ - Initialize a new instance of the TextMemoryPlugin + """Initialize a new instance of the TextMemoryPlugin. Args: - memory (SemanticTextMemoryBase) - the underlying Semantic Text Memory to use - embeddings_kwargs (Optional[Dict[str, Any]]) - the keyword arguments to pass to the embedding generator + memory (SemanticTextMemoryBase): the underlying Semantic Text Memory to use + embeddings_kwargs (Optional[Dict[str, Any]]): the keyword arguments to pass to the embedding generator """ super().__init__(memory=memory, embeddings_kwargs=embeddings_kwargs) @@ -45,17 +44,16 @@ async def recall( ] = DEFAULT_RELEVANCE, limit: Annotated[int, "The maximum number of relevant memories to recall."] = DEFAULT_LIMIT, ) -> str: - """ - Recall a fact from the long term memory. + """Recall a fact from the long term memory. Example: {{memory.recall $ask}} => "Paris" Args: - ask -- The question to ask the memory - collection -- The collection to search for information - relevance -- The relevance score, from 0.0 to 1.0; 1.0 means perfect match - limit -- The maximum number of relevant memories to recall + ask: The question to ask the memory + collection: The collection to search for information + relevance: The relevance score, from 0.0 to 1.0; 1.0 means perfect match + limit: The maximum number of relevant memories to recall Returns: The nearest item from the memory store as a string or empty string if not found. @@ -82,17 +80,15 @@ async def save( key: Annotated[str, "The unique key to associate with the information."], collection: Annotated[str, "The collection to save the information."] = DEFAULT_COLLECTION, ) -> None: - """ - Save a fact to the long term memory. + """Save a fact to the long term memory. Args: - text -- The text to save to the memory - kernel -- The kernel instance, that has a memory store - collection -- The collection to save the information - key -- The unique key to associate with the information + text: The text to save to the memory + kernel: The kernel instance, that has a memory store + collection: The collection to save the information + key: The unique key to associate with the information """ - await self.memory.save_information( collection=collection, text=text, id=key, embeddings_kwargs=self.embeddings_kwargs ) diff --git a/python/semantic_kernel/core_plugins/text_plugin.py b/python/semantic_kernel/core_plugins/text_plugin.py index dc4096df5387..a168f9094170 100644 --- a/python/semantic_kernel/core_plugins/text_plugin.py +++ b/python/semantic_kernel/core_plugins/text_plugin.py @@ -5,8 +5,7 @@ class TextPlugin(KernelBaseModel): - """ - TextPlugin provides a set of functions to manipulate strings. + """TextPlugin provides a set of functions to manipulate strings. Usage: kernel.add_plugin(TextPlugin(), plugin_name="text") @@ -30,8 +29,7 @@ class TextPlugin(KernelBaseModel): @kernel_function(description="Trim whitespace from the start and end of a string.") def trim(self, input: str) -> str: - """ - Trim whitespace from the start and end of a string. + """Trim whitespace from the start and end of a string. Example: KernelArguments["input"] = " hello world " @@ -41,10 +39,9 @@ def trim(self, input: str) -> str: @kernel_function(description="Trim whitespace from the start of a string.") def trim_start(self, input: str) -> str: - """ - Trim whitespace from the start of a string. + """Trim whitespace from the start of a string. - Example: + Example: KernelArguments["input"] = " hello world " {{input.trim $input}} => "hello world " """ @@ -52,10 +49,9 @@ def trim_start(self, input: str) -> str: @kernel_function(description="Trim whitespace from the end of a string.") def trim_end(self, input: str) -> str: - """ - Trim whitespace from the end of a string. + """Trim whitespace from the end of a string. - Example: + Example: KernelArguments["input"] = " hello world " {{input.trim $input}} => " hello world" """ @@ -63,8 +59,7 @@ def trim_end(self, input: str) -> str: @kernel_function(description="Convert a string to uppercase.") def uppercase(self, input: str) -> str: - """ - Convert a string to uppercase. + """Convert a string to uppercase. Example: KernelArguments["input"] = "hello world" @@ -74,10 +69,9 @@ def uppercase(self, input: str) -> str: @kernel_function(description="Convert a string to lowercase.") def lowercase(self, input: str) -> str: - """ - Convert a string to lowercase. + """Convert a string to lowercase. - Example: + Example: KernelArguments["input"] = "HELLO WORLD" {{input.lowercase $input}} => "hello world" """ diff --git a/python/semantic_kernel/core_plugins/time_plugin.py b/python/semantic_kernel/core_plugins/time_plugin.py index 3fd68c579c49..1b8444961644 100644 --- a/python/semantic_kernel/core_plugins/time_plugin.py +++ b/python/semantic_kernel/core_plugins/time_plugin.py @@ -8,9 +8,7 @@ class TimePlugin(KernelBaseModel): - """ - Description: TimePlugin provides a set of functions - to get the current time and date. + """TimePlugin provides a set of functions to get the current time and date. Usage: kernel.add_plugin(TimePlugin(), plugin_name="time") @@ -41,8 +39,7 @@ class TimePlugin(KernelBaseModel): @kernel_function(description="Get the current date.") def date(self) -> str: - """ - Get the current date + """Get the current date. Example: {{time.date}} => Sunday, 12 January, 2031 @@ -52,8 +49,7 @@ def date(self) -> str: @kernel_function(description="Get the current date.") def today(self) -> str: - """ - Get the current date + """Get the current date. Example: {{time.today}} => Sunday, 12 January, 2031 @@ -62,8 +58,7 @@ def today(self) -> str: @kernel_function(description="Get the current date in iso format.") def iso_date(self) -> str: - """ - Get the current date in iso format + """Get the current date in iso format. Example: {{time.iso_date}} => 2031-01-12 @@ -73,8 +68,7 @@ def iso_date(self) -> str: @kernel_function(description="Get the current date and time in the local time zone") def now(self) -> str: - """ - Get the current date and time in the local time zone + """Get the current date and time in the local time zone. Example: {{time.now}} => Sunday, January 12, 2031 9:15 PM @@ -84,8 +78,7 @@ def now(self) -> str: @kernel_function(description="Get the current date and time in UTC", name="utcNow") def utc_now(self) -> str: - """ - Get the current date and time in UTC + """Get the current date and time in UTC. Example: {{time.utcNow}} => Sunday, January 13, 2031 5:15 AM @@ -95,8 +88,7 @@ def utc_now(self) -> str: @kernel_function(description="Get the current time in the local time zone") def time(self) -> str: - """ - Get the current time in the local time zone + """Get the current time in the local time zone. Example: {{time.time}} => 09:15:07 PM @@ -106,8 +98,7 @@ def time(self) -> str: @kernel_function(description="Get the current year") def year(self) -> str: - """ - Get the current year + """Get the current year. Example: {{time.year}} => 2031 @@ -117,8 +108,7 @@ def year(self) -> str: @kernel_function(description="Get the current month") def month(self) -> str: - """ - Get the current month + """Get the current month. Example: {{time.month}} => January @@ -128,8 +118,7 @@ def month(self) -> str: @kernel_function(description="Get the current month number") def month_number(self) -> str: - """ - Get the current month number + """Get the current month number. Example: {{time.monthNumber}} => 01 @@ -139,8 +128,7 @@ def month_number(self) -> str: @kernel_function(description="Get the current day") def day(self) -> str: - """ - Get the current day of the month + """Get the current day of the month. Example: {{time.day}} => 12 @@ -150,8 +138,7 @@ def day(self) -> str: @kernel_function(description="Get the current day of the week", name="dayOfWeek") def day_of_week(self) -> str: - """ - Get the current day of the week + """Get the current day of the week. Example: {{time.dayOfWeek}} => Sunday @@ -161,8 +148,7 @@ def day_of_week(self) -> str: @kernel_function(description="Get the current hour") def hour(self) -> str: - """ - Get the current hour + """Get the current hour. Example: {{time.hour}} => 9 PM @@ -172,8 +158,7 @@ def hour(self) -> str: @kernel_function(description="Get the current hour number", name="hourNumber") def hour_number(self) -> str: - """ - Get the current hour number + """Get the current hour number. Example: {{time.hourNumber}} => 21 @@ -183,8 +168,7 @@ def hour_number(self) -> str: @kernel_function(description="Get the current minute") def minute(self) -> str: - """ - Get the current minute + """Get the current minute. Example: {{time.minute}} => 15 @@ -194,16 +178,14 @@ def minute(self) -> str: @kernel_function(description="Get the date of offset from today by a provided number of days") def days_ago(self, days: str) -> str: - """ - Get the date a provided number of days in the past + """Get the date a provided number of days in the past. - params: + Args: days: The number of days to offset from today - returns: + Returns: The date of the offset day. Example: - KernelContext["input"] = "3" {{time.days_ago $input}} => Sunday, 7 May, 2023 """ d = datetime.date.today() - datetime.timedelta(days=int(days)) @@ -211,16 +193,15 @@ def days_ago(self, days: str) -> str: @kernel_function(description="""Get the date of the last day matching the supplied week day name in English.""") def date_matching_last_day_name(self, day_name: str) -> str: - """ - Get the date of the last day matching the supplied day name + """Get the date of the last day matching the supplied day name. - params: + Args: day_name: The day name to match with. - returns: + + Returns: The date of the matching day. Example: - KernelContext["input"] = "Sunday" {{time.date_matching_last_day_name $input}} => Sunday, 7 May, 2023 """ d = datetime.date.today() @@ -232,8 +213,7 @@ def date_matching_last_day_name(self, day_name: str) -> str: @kernel_function(description="Get the seconds on the current minute") def second(self) -> str: - """ - Get the seconds on the current minute + """Get the seconds on the current minute. Example: {{time.second}} => 7 @@ -243,8 +223,7 @@ def second(self) -> str: @kernel_function(description="Get the current time zone offset", name="timeZoneOffset") def time_zone_offset(self) -> str: - """ - Get the current time zone offset + """Get the current time zone offset. Example: {{time.timeZoneOffset}} => -08:00 @@ -254,8 +233,7 @@ def time_zone_offset(self) -> str: @kernel_function(description="Get the current time zone name", name="timeZoneName") def time_zone_name(self) -> str: - """ - Get the current time zone name + """Get the current time zone name. Example: {{time.timeZoneName}} => PST diff --git a/python/semantic_kernel/core_plugins/wait_plugin.py b/python/semantic_kernel/core_plugins/wait_plugin.py index bd490378135b..71bdb0adc3cb 100644 --- a/python/semantic_kernel/core_plugins/wait_plugin.py +++ b/python/semantic_kernel/core_plugins/wait_plugin.py @@ -9,8 +9,7 @@ class WaitPlugin(KernelBaseModel): - """ - WaitPlugin provides a set of functions to wait for a certain amount of time. + """WaitPlugin provides a set of functions to wait for a certain amount of time. Usage: kernel.add_plugin(WaitPlugin(), plugin_name="wait") @@ -19,8 +18,9 @@ class WaitPlugin(KernelBaseModel): {{wait.wait 5}} => Wait for 5 seconds """ - @kernel_function(description="Wait for a certain number of seconds.") + @kernel_function async def wait(self, input: Annotated[float | str, "The number of seconds to wait, can be str or float."]) -> None: + """Wait for a certain number of seconds.""" if isinstance(input, str): try: input = float(input) diff --git a/python/semantic_kernel/core_plugins/web_search_engine_plugin.py b/python/semantic_kernel/core_plugins/web_search_engine_plugin.py index cf3f848a8867..fd695493ff88 100644 --- a/python/semantic_kernel/core_plugins/web_search_engine_plugin.py +++ b/python/semantic_kernel/core_plugins/web_search_engine_plugin.py @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft. All rights reserved. + from typing import TYPE_CHECKING, Annotated from semantic_kernel.functions.kernel_function_decorator import kernel_function @@ -7,8 +9,7 @@ class WebSearchEnginePlugin: - """ - Description: A plugin that provides web search engine functionality + """A plugin that provides web search engine functionality. Usage: connector = BingConnector(bing_search_api_key) @@ -23,6 +24,7 @@ class WebSearchEnginePlugin: _connector: "ConnectorBase" def __init__(self, connector: "ConnectorBase") -> None: + """Initializes a new instance of the WebSearchEnginePlugin class.""" self._connector = connector @kernel_function(description="Performs a web search for a given query") @@ -32,13 +34,5 @@ async def search( num_results: Annotated[int | None, "The number of search results to return"] = 1, offset: Annotated[int | None, "The number of search results to skip"] = 0, ) -> list[str]: - """ - Returns the search results of the query provided. - Returns `num_results` results and ignores the first `offset`. - - :param query: search query - :param num_results: number of search results to return, default is 1 - :param offset: number of search results to skip, default is 0 - :return: list of search results - """ + """Returns the search results of the query provided.""" return await self._connector.search(query, num_results, offset) diff --git a/python/semantic_kernel/exceptions/function_exceptions.py b/python/semantic_kernel/exceptions/function_exceptions.py index 53248ff56739..5ef6f889ad28 100644 --- a/python/semantic_kernel/exceptions/function_exceptions.py +++ b/python/semantic_kernel/exceptions/function_exceptions.py @@ -12,6 +12,7 @@ class FunctionSyntaxError(FunctionException): class FunctionInitializationError(FunctionException): def __init__(self, message: str): + """Raised when a KernelFunction fails to initialize.""" super().__init__("KernelFunction failed to initialize: " + message) diff --git a/python/semantic_kernel/exceptions/template_engine_exceptions.py b/python/semantic_kernel/exceptions/template_engine_exceptions.py index e7e799a49bd1..ffeae1db29ff 100644 --- a/python/semantic_kernel/exceptions/template_engine_exceptions.py +++ b/python/semantic_kernel/exceptions/template_engine_exceptions.py @@ -18,6 +18,7 @@ class BlockRenderException(BlockException): class VarBlockSyntaxError(BlockSyntaxError): def __init__(self, content: str) -> None: + """Raised when the content of a VarBlock is invalid.""" super().__init__( f"A VarBlock starts with a '$' followed by at least one letter, \ number or underscore, anything else is invalid. \ @@ -31,6 +32,7 @@ class VarBlockRenderError(BlockRenderException): class ValBlockSyntaxError(BlockSyntaxError): def __init__(self, content: str) -> None: + """Raised when the content of a ValBlock is invalid.""" super().__init__( f"A ValBlock starts with a single or double quote followed by at least one letter, \ finishing with the same type of quote as the first one. \ @@ -40,6 +42,7 @@ def __init__(self, content: str) -> None: class NamedArgBlockSyntaxError(BlockSyntaxError): def __init__(self, content: str) -> None: + """Raised when the content of a NamedArgBlock is invalid.""" super().__init__( f"A NamedArgBlock starts with a name (letters, numbers or underscore) \ followed by a single equal sign, then the value of the argument, \ @@ -51,6 +54,7 @@ def __init__(self, content: str) -> None: class FunctionIdBlockSyntaxError(BlockSyntaxError): def __init__(self, content: str) -> None: + """Raised when the content of a FunctionIdBlock is invalid.""" super().__init__( f"A FunctionIdBlock is composed of either a plugin name and \ function name separated by a single dot, or just a function name. \ diff --git a/python/semantic_kernel/filters/kernel_filters_extension.py b/python/semantic_kernel/filters/kernel_filters_extension.py index db6246afd7da..0a0bad083d8f 100644 --- a/python/semantic_kernel/filters/kernel_filters_extension.py +++ b/python/semantic_kernel/filters/kernel_filters_extension.py @@ -105,6 +105,7 @@ def construct_call_stack( filter_type: FilterTypes, inner_function: Callable[[FILTER_CONTEXT_TYPE], Coroutine[Any, Any, None]], ) -> Callable[[FILTER_CONTEXT_TYPE], Coroutine[Any, Any, None]]: + """Construct the call stack for the given filter type.""" stack: list[Any] = [inner_function] for _, filter in getattr(self, FILTER_MAPPING[filter_type]): filter_with_next = partial(filter, next=stack[0]) diff --git a/python/semantic_kernel/functions/function_result.py b/python/semantic_kernel/functions/function_result.py index d065099be729..e225c8916fb6 100644 --- a/python/semantic_kernel/functions/function_result.py +++ b/python/semantic_kernel/functions/function_result.py @@ -16,7 +16,7 @@ class FunctionResult(KernelBaseModel): """The result of a function. - Arguments: + Args: function (KernelFunctionMetadata): The metadata of the function that was invoked. value (Any): The value of the result. metadata (Mapping[str, Any]): The metadata of the result. @@ -56,7 +56,7 @@ def __str__(self) -> str: def get_inner_content(self, index: int = 0) -> Any | None: """Get the inner content of the function result. - Arguments: + Args: index (int): The index of the inner content if the inner content is a list, default 0. """ if isinstance(self.value, list): diff --git a/python/semantic_kernel/functions/kernel_arguments.py b/python/semantic_kernel/functions/kernel_arguments.py index d2241bccb353..d590688849a9 100644 --- a/python/semantic_kernel/functions/kernel_arguments.py +++ b/python/semantic_kernel/functions/kernel_arguments.py @@ -14,18 +14,19 @@ def __init__( ) = None, **kwargs: Any, ): - """Initializes a new instance of the KernelArguments class, - this is a dict-like class with the additional field for the execution_settings. + """Initializes a new instance of the KernelArguments class. + + This is a dict-like class with the additional field for the execution_settings. This class is derived from a dict, hence behaves the same way, just adds the execution_settings as a dict, with service_id and the settings. - Arguments: - settings (PromptExecutionSettings | List[PromptExecutionSettings] | None) -- + Args: + settings (PromptExecutionSettings | List[PromptExecutionSettings] | None): The settings for the execution. If a list is given, make sure all items in the list have a unique service_id as that is used as the key for the dict. - **kwargs (dict[str, Any]) -- The arguments for the function invocation, works similar to a regular dict. + **kwargs (dict[str, Any]): The arguments for the function invocation, works similar to a regular dict. """ super().__init__(**kwargs) settings_dict = None diff --git a/python/semantic_kernel/functions/kernel_function.py b/python/semantic_kernel/functions/kernel_function.py index af2022ac003e..9b7f2a1eb317 100644 --- a/python/semantic_kernel/functions/kernel_function.py +++ b/python/semantic_kernel/functions/kernel_function.py @@ -45,8 +45,7 @@ class KernelFunction(KernelBaseModel): - """ - Semantic Kernel function. + """Semantic Kernel function. Attributes: name (str): The name of the function. Must be upper/lower case letters and @@ -82,9 +81,7 @@ def from_prompt( "PromptExecutionSettings | list[PromptExecutionSettings] | dict[str, PromptExecutionSettings] | None" ) = None, ) -> "KernelFunctionFromPrompt": - """ - Create a new instance of the KernelFunctionFromPrompt class. - """ + """Create a new instance of the KernelFunctionFromPrompt class.""" from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt return KernelFunctionFromPrompt( @@ -105,9 +102,7 @@ def from_method( plugin_name: str | None = None, stream_method: Callable[..., Any] | None = None, ) -> "KernelFunctionFromMethod": - """ - Create a new instance of the KernelFunctionFromMethod class. - """ + """Create a new instance of the KernelFunctionFromMethod class.""" from semantic_kernel.functions.kernel_function_from_method import KernelFunctionFromMethod return KernelFunctionFromMethod( @@ -118,30 +113,37 @@ def from_method( @property def name(self) -> str: + """The name of the function.""" return self.metadata.name @property def plugin_name(self) -> str: + """The name of the plugin that contains this function.""" return self.metadata.plugin_name or "" @property def fully_qualified_name(self) -> str: + """The fully qualified name of the function.""" return self.metadata.fully_qualified_name @property def description(self) -> str | None: + """The description of the function.""" return self.metadata.description @property def is_prompt(self) -> bool: + """Whether the function is based on a prompt.""" return self.metadata.is_prompt @property def parameters(self) -> list["KernelParameterMetadata"]: + """The parameters for the function.""" return self.metadata.parameters @property def return_parameter(self) -> "KernelParameterMetadata | None": + """The return parameter for the function.""" return self.metadata.return_parameter async def __call__( @@ -155,8 +157,9 @@ async def __call__( Args: kernel (Kernel): The kernel - arguments (Optional[KernelArguments]): The Kernel arguments. + arguments (KernelArguments | None): The Kernel arguments. Optional, defaults to None. + metadata (Dict[str, Any]): Additional metadata. kwargs (Dict[str, Any]): Additional keyword arguments that will be Returns: @@ -189,6 +192,7 @@ async def invoke( Args: kernel (Kernel): The kernel arguments (KernelArguments): The Kernel arguments + metadata (Dict[str, Any]): Additional metadata. kwargs (Any): Additional keyword arguments that will be added to the KernelArguments. @@ -224,17 +228,17 @@ async def invoke_stream( metadata: dict[str, Any] = {}, **kwargs: Any, ) -> "AsyncGenerator[FunctionResult | list[StreamingContentMixin | Any], Any]": - """ - Invoke a stream async function with the given arguments. + """Invoke a stream async function with the given arguments. Args: kernel (Kernel): The kernel arguments (KernelArguments): The Kernel arguments + metadata (Dict[str, Any]): Additional metadata. kwargs (Any): Additional keyword arguments that will be added to the KernelArguments. Yields: - KernelContent with the StreamingKernelMixin or FunctionResult -- + KernelContent with the StreamingKernelMixin or FunctionResult: The results of the function, if there is an error a FunctionResult is yielded. """ diff --git a/python/semantic_kernel/functions/kernel_function_decorator.py b/python/semantic_kernel/functions/kernel_function_decorator.py index 5d2696cee21f..fec53e794a48 100644 --- a/python/semantic_kernel/functions/kernel_function_decorator.py +++ b/python/semantic_kernel/functions/kernel_function_decorator.py @@ -14,8 +14,9 @@ def kernel_function( name: str | None = None, description: str | None = None, ) -> Callable[..., Any]: - """ - Decorator for kernel functions, can be used directly as @kernel_function + """Decorator for kernel functions. + + Can be used directly as @kernel_function or with parameters @kernel_function(name='function', description='I am a function.'). This decorator is used to mark a function as a kernel function. It also provides metadata for the function. @@ -37,13 +38,15 @@ def kernel_function( and that is stored as a bool in __kernel_function_streaming__. Args: - name (str | None) -- The name of the function, if not supplied, the function name will be used. - description (str | None) -- The description of the function, + func (Callable[..., object] | None): The function to decorate, can be None (if used as @kernel_function + name (str | None): The name of the function, if not supplied, the function name will be used. + description (str | None): The description of the function, if not supplied, the function docstring will be used, can be None. """ def decorator(func: Callable[..., object]) -> Callable[..., object]: + """The actual decorator function.""" setattr(func, "__kernel_function__", True) setattr(func, "__kernel_function_description__", description or func.__doc__) setattr(func, "__kernel_function_name__", name or getattr(func, "__name__", "unknown")) diff --git a/python/semantic_kernel/functions/kernel_function_extension.py b/python/semantic_kernel/functions/kernel_function_extension.py index 359f6c3b985c..0bb872a377be 100644 --- a/python/semantic_kernel/functions/kernel_function_extension.py +++ b/python/semantic_kernel/functions/kernel_function_extension.py @@ -55,9 +55,9 @@ def add_plugin( description: str | None = None, class_init_arguments: dict[str, dict[str, Any]] | None = None, ) -> "KernelPlugin": - """ - Adds a plugin to the kernel's collection of plugins. If a plugin is provided, - it uses that instance instead of creating a new KernelPlugin. + """Adds a plugin to the kernel's collection of plugins. + + If a plugin is provided, it uses that instance instead of creating a new KernelPlugin. See KernelPlugin.from_directory for more details on how the directory is parsed. Args: @@ -102,8 +102,7 @@ def add_plugin( raise ValueError("plugin or parent_directory must be provided.") def add_plugins(self, plugins: list[KernelPlugin] | dict[str, KernelPlugin | object]) -> None: - """ - Adds a list of plugins to the kernel's collection of plugins. + """Adds a list of plugins to the kernel's collection of plugins. Args: plugins (list[KernelPlugin] | dict[str, KernelPlugin]): The plugins to add to the kernel @@ -131,8 +130,7 @@ def add_function( return_plugin: bool = False, **kwargs: Any, ) -> "KernelFunction | KernelPlugin": - """ - Adds a function to the specified plugin. + """Adds a function to the specified plugin. Args: plugin_name (str): The name of the plugin to add the function to @@ -142,9 +140,7 @@ def add_function( description (str | None): The description of the function prompt (str | None): The prompt template. prompt_template_config (PromptTemplateConfig | None): The prompt template configuration - prompt_execution_settings (PromptExecutionSettings | list[PromptExecutionSettings] - | dict[str, PromptExecutionSettings] | None): - The execution settings, will be parsed into a dict. + prompt_execution_settings: The execution settings, will be parsed into a dict. template_format (str | None): The format of the prompt template prompt_template (PromptTemplateBase | None): The prompt template return_plugin (bool): If True, the plugin is returned instead of the function @@ -190,8 +186,7 @@ def add_functions( plugin_name: str, functions: "list[KERNEL_FUNCTION_TYPE] | dict[str, KERNEL_FUNCTION_TYPE]", ) -> "KernelPlugin": - """ - Adds a list of functions to the specified plugin. + """Adds a list of functions to the specified plugin. Args: plugin_name (str): The name of the plugin to add the functions to @@ -217,9 +212,9 @@ def add_plugin_from_openapi( Args: plugin_name (str): The name of the plugin - plugin_url (str | None): The URL of the plugin - plugin_str (str | None): The JSON string of the plugin - execution_parameters (OpenAIFunctionExecutionParameters | None): The execution parameters + openapi_document_path (str): The path to the OpenAPI document + execution_settings (OpenAPIFunctionExecutionParameters | None): The execution parameters + description (str | None): The description of the plugin Returns: KernelPlugin: The imported plugin @@ -351,8 +346,7 @@ def get_list_of_function_metadata(self, *args: Any, **kwargs: Any) -> list["Kern def get_list_of_function_metadata_bool( self, include_prompt: bool = True, include_native: bool = True ) -> list["KernelFunctionMetadata"]: - """ - Get a list of the function metadata in the plugin collection + """Get a list of the function metadata in the plugin collection. Args: include_prompt (bool): Whether to include semantic functions in the list. diff --git a/python/semantic_kernel/functions/kernel_function_from_method.py b/python/semantic_kernel/functions/kernel_function_from_method.py index 4cf4b33ca398..0e62d238c68d 100644 --- a/python/semantic_kernel/functions/kernel_function_from_method.py +++ b/python/semantic_kernel/functions/kernel_function_from_method.py @@ -20,8 +20,6 @@ class KernelFunctionFromMethod(KernelFunction): """Semantic Kernel Function from a method.""" - # some attributes are now properties, still listed here for documentation purposes - method: Callable[..., Any] stream_method: Callable[..., Any] | None = None @@ -34,8 +32,7 @@ def __init__( return_parameter: KernelParameterMetadata | None = None, additional_metadata: dict[str, Any] | None = None, ) -> None: - """ - Initializes a new instance of the KernelFunctionFromMethod class + """Initializes a new instance of the KernelFunctionFromMethod class. Args: method (Callable[..., Any]): The method to be called diff --git a/python/semantic_kernel/functions/kernel_function_from_prompt.py b/python/semantic_kernel/functions/kernel_function_from_prompt.py index b7145167b443..343384c486cc 100644 --- a/python/semantic_kernel/functions/kernel_function_from_prompt.py +++ b/python/semantic_kernel/functions/kernel_function_from_prompt.py @@ -63,8 +63,7 @@ def __init__( PromptExecutionSettings | list[PromptExecutionSettings] | dict[str, PromptExecutionSettings] ) = None, ) -> None: - """ - Initializes a new instance of the KernelFunctionFromPrompt class + """Initializes a new instance of the KernelFunctionFromPrompt class. Args: function_name (str): The name of the function diff --git a/python/semantic_kernel/functions/kernel_function_metadata.py b/python/semantic_kernel/functions/kernel_function_metadata.py index 0b54525f49c0..67427506bc21 100644 --- a/python/semantic_kernel/functions/kernel_function_metadata.py +++ b/python/semantic_kernel/functions/kernel_function_metadata.py @@ -21,8 +21,7 @@ class KernelFunctionMetadata(KernelBaseModel): @property def fully_qualified_name(self) -> str: - """ - Get the fully qualified name of the function. + """Get the fully qualified name of the function. Returns: The fully qualified name of the function. @@ -30,8 +29,7 @@ def fully_qualified_name(self) -> str: return f"{self.plugin_name}-{self.name}" if self.plugin_name else self.name def __eq__(self, other: object) -> bool: - """ - Compare to another KernelFunctionMetadata instance. + """Compare to another KernelFunctionMetadata instance. Args: other (KernelFunctionMetadata): The other KernelFunctionMetadata instance. diff --git a/python/semantic_kernel/functions/kernel_parameter_metadata.py b/python/semantic_kernel/functions/kernel_parameter_metadata.py index f99e1a095454..60fbe84cba63 100644 --- a/python/semantic_kernel/functions/kernel_parameter_metadata.py +++ b/python/semantic_kernel/functions/kernel_parameter_metadata.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. - from typing import Any from pydantic import Field, model_validator @@ -22,6 +21,7 @@ class KernelParameterMetadata(KernelBaseModel): @model_validator(mode="before") @classmethod def form_schema(cls, data: Any) -> Any: + """Create a schema for the parameter metadata.""" if isinstance(data, dict): if data.get("schema_data") is None: type_object = data.get("type_object", None) @@ -36,6 +36,7 @@ def form_schema(cls, data: Any) -> Any: def infer_schema( cls, type_object: type | None, parameter_type: str | None, default_value: Any, description: str | None ) -> dict[str, Any] | None: + """Infer the schema for the parameter metadata.""" schema = None if type_object is not None: diff --git a/python/semantic_kernel/functions/kernel_plugin.py b/python/semantic_kernel/functions/kernel_plugin.py index cd1f5cd6a239..cdd02b4abaa5 100644 --- a/python/semantic_kernel/functions/kernel_plugin.py +++ b/python/semantic_kernel/functions/kernel_plugin.py @@ -40,8 +40,7 @@ class KernelPlugin(KernelBaseModel): - """ - Represents a Kernel Plugin with functions. + """Represents a Kernel Plugin with functions. This class behaves mostly like a dictionary, with functions as values and their names as keys. When you add a function, through `.set` or `__setitem__`, the function is copied, the metadata is deep-copied @@ -104,7 +103,7 @@ def __init__( | None ) = None, ): - """Create a KernelPlugin + """Create a KernelPlugin. Args: name: The name of the plugin. The name can be upper/lower @@ -184,6 +183,7 @@ def update(self, *args: Any, **kwargs: KernelFunction) -> None: @singledispatchmethod def add(self, functions: Any) -> None: + """Add functions to the plugin.""" raise TypeError(f"Unknown type being added, type was {type(functions)}") @add.register(list) @@ -203,6 +203,7 @@ def add_dict(self, functions: dict[str, KERNEL_FUNCTION_TYPE]) -> None: self[name] = function def setdefault(self, key: str, value: KernelFunction | None = None): + """Set a default value for a key.""" if key not in self.functions: if value is None: raise ValueError("Value must be provided for new key.") @@ -214,14 +215,14 @@ def __iter__(self) -> Generator[KernelFunction, None, None]: # type: ignore yield from self.functions.values() def __contains__(self, key: str) -> bool: + """Check if a function is in the plugin.""" return key in self.functions # endregion # region Properties def get_functions_metadata(self) -> list["KernelFunctionMetadata"]: - """ - Get the metadata for the functions in the plugin. + """Get the metadata for the functions in the plugin. Returns: A list of KernelFunctionMetadata instances. @@ -233,16 +234,19 @@ def get_functions_metadata(self) -> list["KernelFunctionMetadata"]: @classmethod def from_object( - cls, plugin_name: str, plugin_instance: Any | dict[str, Any], description: str | None = None + cls, + plugin_name: str, + plugin_instance: Any | dict[str, Any], + description: str | None = None, ) -> "KernelPlugin": - """ - Creates a plugin that wraps the specified target object and imports it into the kernel's plugin collection + """Creates a plugin that wraps the specified target object and imports it into the kernel's plugin collection. Args: + plugin_name (str): The name of the plugin. Allows chars: upper, lower ASCII and underscores. plugin_instance (Any | dict[str, Any]): The plugin instance. This can be a custom class or a dictionary of classes that contains methods with the kernel_function decorator for one or several methods. See `TextMemoryPlugin` as an example. - plugin_name (str): The name of the plugin. Allows chars: upper, lower ASCII and underscores. + description (str | None): The description of the plugin. Returns: KernelPlugin: The imported plugin of type KernelPlugin. @@ -365,9 +369,8 @@ def from_openapi( Args: plugin_name (str): The name of the plugin - plugin_url (str | None): The URL of the plugin - plugin_str (str | None): The JSON string of the plugin - execution_parameters (OpenAIFunctionExecutionParameters | None): The execution parameters + openapi_document_path (str): The path to the OpenAPI document + execution_settings (OpenAPIFunctionExecutionParameters | None): The execution parameters description (str | None): The description of the plugin Returns: @@ -376,7 +379,6 @@ def from_openapi( Raises: PluginInitializationError: if the plugin URL or plugin JSON/YAML is not provided """ - if not openapi_document_path: raise PluginInitializationError("OpenAPI document path is required.") @@ -406,6 +408,7 @@ async def from_openai( plugin_url (str | None): The URL of the plugin plugin_str (str | None): The JSON string of the plugin execution_parameters (OpenAIFunctionExecutionParameters | None): The execution parameters + description (str | None): The description of the plugin Returns: KernelPlugin: The created plugin @@ -413,7 +416,6 @@ async def from_openai( Raises: PluginInitializationError: if the plugin URL or plugin JSON/YAML is not provided """ - if execution_parameters is None: execution_parameters = OpenAIFunctionExecutionParameters() @@ -463,6 +465,7 @@ def from_python_file( description: str | None = None, class_init_arguments: dict[str, dict[str, Any]] | None = None, ) -> "KernelPlugin": + """Create a plugin from a Python file.""" module_name = os.path.basename(py_file).replace(".py", "") spec = importlib.util.spec_from_file_location(module_name, py_file) if not spec: diff --git a/python/semantic_kernel/functions/prompt_rendering_result.py b/python/semantic_kernel/functions/prompt_rendering_result.py index e4b1d52b5fc7..a7e1d1b6d1cb 100644 --- a/python/semantic_kernel/functions/prompt_rendering_result.py +++ b/python/semantic_kernel/functions/prompt_rendering_result.py @@ -7,8 +7,7 @@ class PromptRenderingResult(KernelBaseModel): - """ - Represents the result of rendering a prompt template. + """Represents the result of rendering a prompt template. Attributes: rendered_prompt (str): The rendered prompt. diff --git a/python/semantic_kernel/kernel.py b/python/semantic_kernel/kernel.py index 53c84a979f4d..f1faf63d9ed2 100644 --- a/python/semantic_kernel/kernel.py +++ b/python/semantic_kernel/kernel.py @@ -32,8 +32,9 @@ class Kernel(KernelFilterExtension, KernelFunctionExtension, KernelServicesExtension, KernelReliabilityExtension): - """ - The Kernel class is the main entry point for the Semantic Kernel. It provides the ability to run + """The main Kernel class of Semantic Kernel. + + This is the main entry point for the Semantic Kernel. It provides the ability to run semantic/native functions, and manage plugins, memory, and AI services. Attributes: @@ -52,15 +53,15 @@ def __init__( ai_service_selector: AIServiceSelector | None = None, **kwargs: Any, ) -> None: - """ - Initialize a new instance of the Kernel class. + """Initialize a new instance of the Kernel class. Args: plugins (KernelPlugin | dict[str, KernelPlugin] | list[KernelPlugin] | None): The plugins to be used by the kernel, will be rewritten to a dict with plugin name as key - services (AIServiceClientBase | list[AIServiceClientBase] | dict[str, AIServiceClientBase] | None: + services (AIServiceClientBase | list[AIServiceClientBase] | dict[str, AIServiceClientBase] | None): The services to be used by the kernel, will be rewritten to a dict with service_id as key - ai_service_selector (AIServiceSelector | None): The AI service selector to be used by the kernel, + ai_service_selector (AIServiceSelector | None): + The AI service selector to be used by the kernel, default is based on order of execution settings. **kwargs (Any): Additional fields to be passed to the Kernel model, these are limited to retry_mechanism and function_invoking_handlers @@ -92,11 +93,11 @@ async def invoke_stream( This will execute the functions in the order they are provided, if a list of functions is provided. When multiple functions are provided only the last one is streamed, the rest is executed as a pipeline. - Arguments: - functions (KernelFunction): The function or functions to execute, - this value has precedence when supplying both this and using function_name and plugin_name, - if this is none, function_name and plugin_name are used and cannot be None. - arguments (KernelArguments): The arguments to pass to the function(s), optional + Args: + function (KernelFunction): The function to execute, + this value has precedence when supplying both this and using function_name and plugin_name, + if this is none, function_name and plugin_name are used and cannot be None. + arguments (KernelArguments | None): The arguments to pass to the function(s), optional function_name (str | None): The name of the function to execute plugin_name (str | None): The name of the plugin to execute metadata (dict[str, Any]): The metadata to pass to the function(s) @@ -151,7 +152,7 @@ async def invoke( When multiple functions are passed the FunctionResult of each is put into a list. - Arguments: + Args: function (KernelFunction): The function or functions to execute, this value has precedence when supplying both this and using function_name and plugin_name, if this is none, function_name and plugin_name are used and cannot be None. @@ -201,8 +202,7 @@ async def invoke_prompt( ] = KERNEL_TEMPLATE_FORMAT_NAME, **kwargs: Any, ) -> FunctionResult | None: - """ - Invoke a function from the provided prompt + """Invoke a function from the provided prompt. Args: function_name (str): The name of the function @@ -242,8 +242,7 @@ async def invoke_prompt_stream( return_function_results: bool | None = False, **kwargs: Any, ) -> AsyncIterable[list["StreamingContentMixin"] | FunctionResult | list[FunctionResult]]: - """ - Invoke a function from the provided prompt and stream the results + """Invoke a function from the provided prompt and stream the results. Args: function_name (str): The name of the function @@ -251,6 +250,7 @@ async def invoke_prompt_stream( prompt (str): The prompt to use arguments (KernelArguments | None): The arguments to pass to the function(s), optional template_format (str | None): The format of the prompt template + return_function_results (bool): If True, the function results are yielded as a list[FunctionResult] kwargs (dict[str, Any]): arguments that can be used instead of supplying KernelArguments Returns: diff --git a/python/semantic_kernel/memory/memory_query_result.py b/python/semantic_kernel/memory/memory_query_result.py index df79547eaa68..1147ee8c91aa 100644 --- a/python/semantic_kernel/memory/memory_query_result.py +++ b/python/semantic_kernel/memory/memory_query_result.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. - from numpy import ndarray from semantic_kernel.memory.memory_record import MemoryRecord @@ -31,17 +30,18 @@ def __init__( ) -> None: """Initialize a new instance of MemoryQueryResult. - Arguments: - is_reference {bool} -- Whether the record is a reference record. - external_source_name {Optional[str]} -- The name of the external source. - id {str} -- A unique for the record. - description {Optional[str]} -- The description of the record. - text {Optional[str]} -- The text of the record. - embedding {ndarray} -- The embedding of the record. - relevance {float} -- The relevance of the record to a known query. + Args: + is_reference (bool): Whether the record is a reference record. + external_source_name (Optional[str]): The name of the external source. + id (str): A unique for the record. + description (Optional[str]): The description of the record. + text (Optional[str]): The text of the record. + additional_metadata (Optional[str]): Custom metadata for the record. + embedding (ndarray): The embedding of the record. + relevance (float): The relevance of the record to a known query. Returns: - None -- None. + None: None. """ self.is_reference = is_reference self.external_source_name = external_source_name @@ -59,12 +59,12 @@ def from_memory_record( ) -> "MemoryQueryResult": """Create a new instance of MemoryQueryResult from a MemoryRecord. - Arguments: - record {MemoryRecord} -- The MemoryRecord to create the MemoryQueryResult from. - relevance {float} -- The relevance of the record to a known query. + Args: + record (MemoryRecord): The MemoryRecord to create the MemoryQueryResult from. + relevance (float): The relevance of the record to a known query. Returns: - MemoryQueryResult -- The created MemoryQueryResult. + MemoryQueryResult: The created MemoryQueryResult. """ return MemoryQueryResult( is_reference=record._is_reference, diff --git a/python/semantic_kernel/memory/memory_record.py b/python/semantic_kernel/memory/memory_record.py index 9346acc94a2b..a6234605ad0b 100644 --- a/python/semantic_kernel/memory/memory_record.py +++ b/python/semantic_kernel/memory/memory_record.py @@ -33,17 +33,16 @@ def __init__( ) -> None: """Initialize a new instance of MemoryRecord. - Arguments: - is_reference {bool} -- Whether the record is a reference record. - external_source_name {Optional[str]} -- The name of the external source. - id {str} -- A unique for the record. - description {Optional[str]} -- The description of the record. - text {Optional[str]} -- The text of the record. - additional_metadata {Optional[str]} -- Custom metadata for the record. - embedding {ndarray} -- The embedding of the record. - - Returns: - None -- None. + Args: + is_reference (bool): Whether the record is a reference record. + external_source_name (Optional[str]): The name of the external source. + id (str): A unique for the record. + description (Optional[str]): The description of the record. + text (Optional[str]): The text of the record. + additional_metadata (Optional[str]): Custom metadata for the record. + embedding (ndarray): The embedding of the record. + key (Optional[str]): The key of the record. + timestamp (Optional[datetime]): The timestamp of the record. """ self._key = key self._timestamp = timestamp @@ -65,15 +64,15 @@ def reference_record( ) -> "MemoryRecord": """Create a reference record. - Arguments: - external_id {str} -- The external id of the record. - source_name {str} -- The name of the external source. - description {Optional[str]} -- The description of the record. - additional_metadata {Optional[str]} -- Custom metadata for the record. - embedding {ndarray} -- The embedding of the record. + Args: + external_id (str): The external id of the record. + source_name (str): The name of the external source. + description (Optional[str]): The description of the record. + additional_metadata (Optional[str]): Custom metadata for the record. + embedding (ndarray): The embedding of the record. Returns: - MemoryRecord -- The reference record. + MemoryRecord: The reference record. """ return MemoryRecord( is_reference=True, @@ -96,16 +95,16 @@ def local_record( ) -> "MemoryRecord": """Create a local record. - Arguments: - id {str} -- A unique for the record. - text {str} -- The text of the record. - description {Optional[str]} -- The description of the record. - additional_metadata {Optional[str]} -- Custom metadata for the record. - embedding {ndarray} -- The embedding of the record. - timestamp {Optional[datetime]} -- The timestamp of the record. + Args: + id (str): A unique for the record. + text (str): The text of the record. + description (Optional[str]): The description of the record. + additional_metadata (Optional[str]): Custom metadata for the record. + embedding (ndarray): The embedding of the record. + timestamp (Optional[datetime]): The timestamp of the record. Returns: - MemoryRecord -- The local record. + MemoryRecord: The local record. """ return MemoryRecord( is_reference=False, @@ -120,24 +119,30 @@ def local_record( @property def id(self): + """Get the unique identifier for the memory record.""" return self._id @property def embedding(self) -> ndarray: + """Get the embedding of the memory record.""" return self._embedding @property def text(self): + """Get the text of the memory record.""" return self._text @property def additional_metadata(self): + """Get the additional metadata of the memory record.""" return self._additional_metadata @property def description(self): + """Get the description of the memory record.""" return self._description @property def timestamp(self): + """Get the timestamp of the memory record.""" return self._timestamp diff --git a/python/semantic_kernel/memory/memory_store_base.py b/python/semantic_kernel/memory/memory_store_base.py index 585b2410f55a..b1b695e81665 100644 --- a/python/semantic_kernel/memory/memory_store_base.py +++ b/python/semantic_kernel/memory/memory_store_base.py @@ -11,24 +11,23 @@ @experimental_class class MemoryStoreBase(ABC): async def __aenter__(self): + """Enter the context manager.""" return self async def __aexit__(self, *args): + """Exit the context manager.""" await self.close() async def close(self): - """Async close connection, invoked by MemoryStoreBase.__aexit__()""" + """Close the connection.""" pass @abstractmethod async def create_collection(self, collection_name: str) -> None: """Creates a new collection in the data store. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - - Returns: - None + Args: + collection_name (str): The name associated with a collection of embeddings. """ pass @@ -39,7 +38,7 @@ async def get_collections( """Gets all collection names in the data store. Returns: - List[str] -- A group of collection names. + List[str]: A group of collection names. """ pass @@ -47,11 +46,8 @@ async def get_collections( async def delete_collection(self, collection_name: str) -> None: """Deletes a collection from the data store. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - - Returns: - None + Args: + collection_name (str): The name associated with a collection of embeddings. """ pass @@ -59,42 +55,45 @@ async def delete_collection(self, collection_name: str) -> None: async def does_collection_exist(self, collection_name: str) -> bool: """Determines if a collection exists in the data store. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. + Args: + collection_name (str): The name associated with a collection of embeddings. Returns: - bool -- True if given collection exists, False if not. + bool: True if given collection exists, False if not. """ - pass @abstractmethod async def upsert(self, collection_name: str, record: MemoryRecord) -> str: - """Upserts a memory record into the data store. Does not guarantee that the collection exists. - If the record already exists, it will be updated. - If the record does not exist, it will be created. + """Upserts a memory record into the data store. + + Does not guarantee that the collection exists. + If the record already exists, it will be updated. + If the record does not exist, it will be created. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - record {MemoryRecord} -- The memory record to upsert. + Args: + collection_name (str): The name associated with a collection of embeddings. + record (MemoryRecord): The memory record to upsert. Returns: - str -- The unique identifier for the memory record. + str: The unique identifier for the memory record. """ pass @abstractmethod async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: - """Upserts a group of memory records into the data store. Does not guarantee that the collection exists. - If the record already exists, it will be updated. - If the record does not exist, it will be created. + """Upserts a group of memory records into the data store. + + Does not guarantee that the collection exists. + If the record already exists, it will be updated. + If the record does not exist, it will be created. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - records {MemoryRecord} -- The memory records to upsert. + Args: + collection_name (str): The name associated with a collection of embeddings. + records (MemoryRecord): The memory records to upsert. Returns: - List[str] -- The unique identifiers for the memory records. + List[str]: The unique identifiers for the memory records. """ pass @@ -102,27 +101,32 @@ async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) async def get(self, collection_name: str, key: str, with_embedding: bool) -> MemoryRecord: """Gets a memory record from the data store. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - key {str} -- The unique id associated with the memory record to get. - with_embedding {bool} -- If true, the embedding will be returned in the memory record. + Args: + collection_name (str): The name associated with a collection of embeddings. + key (str): The unique id associated with the memory record to get. + with_embedding (bool): If true, the embedding will be returned in the memory record. Returns: - MemoryRecord -- The memory record if found + MemoryRecord: The memory record if found """ pass @abstractmethod - async def get_batch(self, collection_name: str, keys: list[str], with_embeddings: bool) -> list[MemoryRecord]: + async def get_batch( + self, + collection_name: str, + keys: list[str], + with_embeddings: bool, + ) -> list[MemoryRecord]: """Gets a batch of memory records from the data store. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - keys {List[str]} -- The unique ids associated with the memory records to get. - with_embeddings {bool} -- If true, the embedding will be returned in the memory records. + Args: + collection_name (str): The name associated with a collection of embeddings. + keys (List[str]): The unique ids associated with the memory records to get. + with_embeddings (bool): If true, the embedding will be returned in the memory records. Returns: - List[MemoryRecord] -- The memory records associated with the unique keys provided. + List[MemoryRecord]: The memory records associated with the unique keys provided. """ pass @@ -130,12 +134,9 @@ async def get_batch(self, collection_name: str, keys: list[str], with_embeddings async def remove(self, collection_name: str, key: str) -> None: """Removes a memory record from the data store. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - key {str} -- The unique id associated with the memory record to remove. - - Returns: - None + Args: + collection_name (str): The name associated with a collection of embeddings. + key (str): The unique id associated with the memory record to remove. """ pass @@ -143,12 +144,9 @@ async def remove(self, collection_name: str, key: str) -> None: async def remove_batch(self, collection_name: str, keys: list[str]) -> None: """Removes a batch of memory records from the data store. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - keys {List[str]} -- The unique ids associated with the memory records to remove. - - Returns: - None + Args: + collection_name (str): The name associated with a collection of embeddings. + keys (List[str]): The unique ids associated with the memory records to remove. """ pass @@ -163,15 +161,15 @@ async def get_nearest_matches( ) -> list[tuple[MemoryRecord, float]]: """Gets the nearest matches to an embedding of type float. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - embedding {ndarray} -- The embedding to compare the collection's embeddings with. - limit {int} -- The maximum number of similarity results to return. - min_relevance_score {float} -- The minimum relevance threshold for returned results. - with_embeddings {bool} -- If true, the embeddings will be returned in the memory records. + Args: + collection_name (str): The name associated with a collection of embeddings. + embedding (ndarray): The embedding to compare the collection's embeddings with. + limit (int): The maximum number of similarity results to return. + min_relevance_score (float): The minimum relevance threshold for returned results. + with_embeddings (bool): If true, the embeddings will be returned in the memory records. Returns: - List[Tuple[MemoryRecord, float]] -- A list of tuples where item1 is a MemoryRecord and item2 + List[Tuple[MemoryRecord, float]]: A list of tuples where item1 is a MemoryRecord and item2 is its similarity score as a float. """ pass @@ -186,13 +184,13 @@ async def get_nearest_match( ) -> tuple[MemoryRecord, float]: """Gets the nearest match to an embedding of type float. Does not guarantee that the collection exists. - Arguments: - collection_name {str} -- The name associated with a collection of embeddings. - embedding {ndarray} -- The embedding to compare the collection's embeddings with. - min_relevance_score {float} -- The minimum relevance threshold for returned result. - with_embedding {bool} -- If true, the embeddings will be returned in the memory record. + Args: + collection_name (str): The name associated with a collection of embeddings. + embedding (ndarray): The embedding to compare the collection's embeddings with. + min_relevance_score (float): The minimum relevance threshold for returned result. + with_embedding (bool): If true, the embeddings will be returned in the memory record. Returns: - Tuple[MemoryRecord, float] -- A tuple consisting of the MemoryRecord and the similarity score as a float. + Tuple[MemoryRecord, float]: A tuple consisting of the MemoryRecord and the similarity score as a float. """ pass diff --git a/python/semantic_kernel/memory/null_memory.py b/python/semantic_kernel/memory/null_memory.py index 4ac271ac7533..73cfb7097f17 100644 --- a/python/semantic_kernel/memory/null_memory.py +++ b/python/semantic_kernel/memory/null_memory.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. - from semantic_kernel.memory.memory_query_result import MemoryQueryResult from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase from semantic_kernel.utils.experimental_decorator import experimental_class @@ -16,7 +15,7 @@ async def save_information( description: str | None = None, additional_metadata: str | None = None, ) -> None: - """Nullifies behavior of SemanticTextMemoryBase.save_information()""" + """Nullifies behavior of SemanticTextMemoryBase save_information.""" return None async def save_reference( @@ -28,11 +27,11 @@ async def save_reference( description: str | None = None, additional_metadata: str | None = None, ) -> None: - """Nullifies behavior of SemanticTextMemoryBase.save_reference()""" + """Nullifies behavior of SemanticTextMemoryBase save_reference.""" return None async def get(self, collection: str, query: str) -> MemoryQueryResult | None: - """Nullifies behavior of SemanticTextMemoryBase.get()""" + """Nullifies behavior of SemanticTextMemoryBase get.""" return None async def search( @@ -42,11 +41,11 @@ async def search( limit: int = 1, min_relevance_score: float = 0.7, ) -> list[MemoryQueryResult]: - """Nullifies behavior of SemanticTextMemoryBase.search()""" + """Nullifies behavior of SemanticTextMemoryBase search.""" return [] async def get_collections(self) -> list[str]: - """Nullifies behavior of SemanticTextMemoryBase.get_collections()""" + """Nullifies behavior of SemanticTextMemoryBase get_collections.""" return [] diff --git a/python/semantic_kernel/memory/semantic_text_memory.py b/python/semantic_kernel/memory/semantic_text_memory.py index 2b27626a2d98..4a99a47e4bed 100644 --- a/python/semantic_kernel/memory/semantic_text_memory.py +++ b/python/semantic_kernel/memory/semantic_text_memory.py @@ -21,13 +21,10 @@ class SemanticTextMemory(SemanticTextMemoryBase): def __init__(self, storage: MemoryStoreBase, embeddings_generator: EmbeddingGeneratorBase) -> None: """Initialize a new instance of SemanticTextMemory. - Arguments: - storage {MemoryStoreBase} -- The MemoryStoreBase to use for storage. - embeddings_generator {EmbeddingGeneratorBase} -- The EmbeddingGeneratorBase + Args: + storage (MemoryStoreBase): The MemoryStoreBase to use for storage. + embeddings_generator (EmbeddingGeneratorBase): The EmbeddingGeneratorBase to use for generating embeddings. - - Returns: - None -- None. """ super().__init__() self._storage = storage @@ -44,14 +41,13 @@ async def save_information( ) -> None: """Save information to the memory (calls the memory store's upsert method). - Arguments: - collection {str} -- The collection to save the information to. - text {str} -- The text to save. - id {str} -- The id of the information. - description {Optional[str]} -- The description of the information. - - Returns: - None -- None. + Args: + collection (str): The collection to save the information to. + text (str): The text to save. + id (str): The id of the information. + description (Optional[str]): The description of the information. + additional_metadata (Optional[str]): Additional metadata of the information. + embeddings_kwargs (Optional[Dict[str, Any]]): The embeddings kwargs of the information. """ # TODO: not the best place to create collection, but will address this behavior together with .NET SK if not await self._storage.does_collection_exist(collection_name=collection): @@ -80,15 +76,14 @@ async def save_reference( ) -> None: """Save a reference to the memory (calls the memory store's upsert method). - Arguments: - collection {str} -- The collection to save the reference to. - text {str} -- The text to save. - external_id {str} -- The external id of the reference. - external_source_name {str} -- The external source name of the reference. - description {Optional[str]} -- The description of the reference. - - Returns: - None -- None. + Args: + collection (str): The collection to save the reference to. + text (str): The text to save. + external_id (str): The external id of the reference. + external_source_name (str): The external source name of the reference. + description (Optional[str]): The description of the reference. + additional_metadata (Optional[str]): Additional metadata of the reference. + embeddings_kwargs (Optional[Dict[str, Any]]): The embeddings kwargs of the reference. """ # TODO: not the best place to create collection, but will address this behavior together with .NET SK if not await self._storage.does_collection_exist(collection_name=collection): @@ -112,12 +107,12 @@ async def get( ) -> MemoryQueryResult | None: """Get information from the memory (calls the memory store's get method). - Arguments: - collection {str} -- The collection to get the information from. - key {str} -- The key of the information. + Args: + collection (str): The collection to get the information from. + key (str): The key of the information. Returns: - Optional[MemoryQueryResult] -- The MemoryQueryResult if found, None otherwise. + Optional[MemoryQueryResult]: The MemoryQueryResult if found, None otherwise. """ record = await self._storage.get(collection_name=collection, key=key) return MemoryQueryResult.from_memory_record(record, 1.0) if record else None @@ -133,15 +128,16 @@ async def search( ) -> list[MemoryQueryResult]: """Search the memory (calls the memory store's get_nearest_matches method). - Arguments: - collection {str} -- The collection to search in. - query {str} -- The query to search for. - limit {int} -- The maximum number of results to return. (default: {1}) - min_relevance_score {float} -- The minimum relevance score to return. (default: {0.0}) - with_embeddings {bool} -- Whether to return the embeddings of the results. (default: {False}) + Args: + collection (str): The collection to search in. + query (str): The query to search for. + limit (int): The maximum number of results to return. (default: {1}) + min_relevance_score (float): The minimum relevance score to return. (default: {0.0}) + with_embeddings (bool): Whether to return the embeddings of the results. (default: {False}) + embeddings_kwargs (Optional[Dict[str, Any]]): The embeddings kwargs of the information. Returns: - List[MemoryQueryResult] -- The list of MemoryQueryResult found. + List[MemoryQueryResult]: The list of MemoryQueryResult found. """ query_embedding = (await self._embeddings_generator.generate_embeddings([query], **embeddings_kwargs))[0] results = await self._storage.get_nearest_matches( @@ -158,6 +154,6 @@ async def get_collections(self) -> list[str]: """Get the list of collections in the memory (calls the memory store's get_collections method). Returns: - List[str] -- The list of all the memory collection names. + List[str]: The list of all the memory collection names. """ return await self._storage.get_collections() diff --git a/python/semantic_kernel/memory/semantic_text_memory_base.py b/python/semantic_kernel/memory/semantic_text_memory_base.py index de5fb0dcfb86..a3e00edd800c 100644 --- a/python/semantic_kernel/memory/semantic_text_memory_base.py +++ b/python/semantic_kernel/memory/semantic_text_memory_base.py @@ -1,12 +1,14 @@ # Copyright (c) Microsoft. All rights reserved. from abc import abstractmethod -from typing import Any, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from semantic_kernel.kernel_pydantic import KernelBaseModel -from semantic_kernel.memory.memory_query_result import MemoryQueryResult from semantic_kernel.utils.experimental_decorator import experimental_class +if TYPE_CHECKING: + from semantic_kernel.memory.memory_query_result import MemoryQueryResult + SemanticTextMemoryT = TypeVar("SemanticTextMemoryT", bound="SemanticTextMemoryBase") @@ -21,18 +23,17 @@ async def save_information( description: str | None = None, additional_metadata: str | None = None, embeddings_kwargs: dict[str, Any] | None = None, - # TODO: ctoken? ) -> None: """Save information to the memory (calls the memory store's upsert method). - Arguments: - collection {str} -- The collection to save the information to. - text {str} -- The text to save. - id {str} -- The id of the information. - description {Optional[str]} -- The description of the information. + Args: + collection (str): The collection to save the information to. + text (str): The text to save. + id (str): The id of the information. + description (Optional[str]): The description of the information. + additional_metadata (Optional[str]): Additional metadata of the information. + embeddings_kwargs (Optional[Dict[str, Any]]): The embeddings kwargs of the information. - Returns: - None -- None. """ pass @@ -48,15 +49,14 @@ async def save_reference( ) -> None: """Save a reference to the memory (calls the memory store's upsert method). - Arguments: - collection {str} -- The collection to save the reference to. - text {str} -- The text to save. - external_id {str} -- The external id of the reference. - external_source_name {str} -- The external source name of the reference. - description {Optional[str]} -- The description of the reference. + Args: + collection (str): The collection to save the reference to. + text (str): The text to save. + external_id (str): The external id of the reference. + external_source_name (str): The external source name of the reference. + description (Optional[str]): The description of the reference. + additional_metadata (Optional[str]): Additional metadata of the reference. - Returns: - None -- None. """ pass @@ -66,15 +66,15 @@ async def get( collection: str, key: str, # TODO: with_embedding: bool, - ) -> MemoryQueryResult | None: + ) -> "MemoryQueryResult | None": """Get information from the memory (calls the memory store's get method). - Arguments: - collection {str} -- The collection to get the information from. - key {str} -- The key of the information. + Args: + collection (str): The collection to get the information from. + key (str): The key of the information. Returns: - Optional[MemoryQueryResult] -- The MemoryQueryResult if found, None otherwise. + Optional[MemoryQueryResult]: The MemoryQueryResult if found, None otherwise. """ pass @@ -85,19 +85,18 @@ async def search( query: str, limit: int = 1, min_relevance_score: float = 0.7, - # TODO: ctoken? - ) -> list[MemoryQueryResult]: + ) -> list["MemoryQueryResult"]: """Search the memory (calls the memory store's get_nearest_matches method). - Arguments: - collection {str} -- The collection to search in. - query {str} -- The query to search for. - limit {int} -- The maximum number of results to return. (default: {1}) - min_relevance_score {float} -- The minimum relevance score to return. (default: {0.0}) - with_embeddings {bool} -- Whether to return the embeddings of the results. (default: {False}) + Args: + collection (str): The collection to search in. + query (str): The query to search for. + limit (int): The maximum number of results to return. (default: {1}) + min_relevance_score (float): The minimum relevance score to return. (default: {0.0}) + with_embeddings (bool): Whether to return the embeddings of the results. (default: {False}) Returns: - List[MemoryQueryResult] -- The list of MemoryQueryResult found. + List[MemoryQueryResult]: The list of MemoryQueryResult found. """ pass @@ -106,6 +105,6 @@ async def get_collections(self) -> list[str]: """Get the list of collections in the memory (calls the memory store's get_collections method). Returns: - List[str] -- The list of all the memory collection names. + List[str]: The list of all the memory collection names. """ pass diff --git a/python/semantic_kernel/memory/volatile_memory_store.py b/python/semantic_kernel/memory/volatile_memory_store.py index 4b967658c912..13a207f3ce04 100644 --- a/python/semantic_kernel/memory/volatile_memory_store.py +++ b/python/semantic_kernel/memory/volatile_memory_store.py @@ -24,8 +24,8 @@ def __init__(self) -> None: async def create_collection(self, collection_name: str) -> None: """Creates a new collection if it does not exist. - Arguments: - collection_name {str} -- The name of the collection to create. + Args: + collection_name (str): The name of the collection to create. Returns: None @@ -41,15 +41,15 @@ async def get_collections( """Gets the list of collections. Returns: - List[str] -- The list of collections. + List[str]: The list of collections. """ return list(self._store.keys()) async def delete_collection(self, collection_name: str) -> None: """Deletes a collection. - Arguments: - collection_name {str} -- The name of the collection to delete. + Args: + collection_name (str): The name of the collection to delete. Returns: None @@ -60,23 +60,23 @@ async def delete_collection(self, collection_name: str) -> None: async def does_collection_exist(self, collection_name: str) -> bool: """Checks if a collection exists. - Arguments: - collection_name {str} -- The name of the collection to check. + Args: + collection_name (str): The name of the collection to check. Returns: - bool -- True if the collection exists; otherwise, False. + bool: True if the collection exists; otherwise, False. """ return collection_name in self._store async def upsert(self, collection_name: str, record: MemoryRecord) -> str: """Upserts a record. - Arguments: - collection_name {str} -- The name of the collection to upsert the record into. - record {MemoryRecord} -- The record to upsert. + Args: + collection_name (str): The name of the collection to upsert the record into. + record (MemoryRecord): The record to upsert. Returns: - str -- The unique database key of the record. + str: The unique database key of the record. """ if collection_name not in self._store: raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist") @@ -88,12 +88,12 @@ async def upsert(self, collection_name: str, record: MemoryRecord) -> str: async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]: """Upserts a batch of records. - Arguments: - collection_name {str} -- The name of the collection to upsert the records into. - records {List[MemoryRecord]} -- The records to upsert. + Args: + collection_name (str): The name of the collection to upsert the records into. + records (List[MemoryRecord]): The records to upsert. Returns: - List[str] -- The unique database keys of the records. + List[str]: The unique database keys of the records. """ if collection_name not in self._store: raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist") @@ -106,13 +106,13 @@ async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord: """Gets a record. - Arguments: - collection_name {str} -- The name of the collection to get the record from. - key {str} -- The unique database key of the record. - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the record from. + key (str): The unique database key of the record. + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - MemoryRecord -- The record. + MemoryRecord: The record. """ if collection_name not in self._store: raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist") @@ -133,13 +133,13 @@ async def get_batch( ) -> list[MemoryRecord]: """Gets a batch of records. - Arguments: - collection_name {str} -- The name of the collection to get the records from. - keys {List[str]} -- The unique database keys of the records. - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + Args: + collection_name (str): The name of the collection to get the records from. + keys (List[str]): The unique database keys of the records. + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[MemoryRecord] -- The records. + List[MemoryRecord]: The records. """ if collection_name not in self._store: raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist") @@ -156,9 +156,9 @@ async def get_batch( async def remove(self, collection_name: str, key: str) -> None: """Removes a record. - Arguments: - collection_name {str} -- The name of the collection to remove the record from. - key {str} -- The unique database key of the record to remove. + Args: + collection_name (str): The name of the collection to remove the record from. + key (str): The unique database key of the record to remove. Returns: None @@ -174,9 +174,9 @@ async def remove(self, collection_name: str, key: str) -> None: async def remove_batch(self, collection_name: str, keys: list[str]) -> None: """Removes a batch of records. - Arguments: - collection_name {str} -- The name of the collection to remove the records from. - keys {List[str]} -- The unique database keys of the records to remove. + Args: + collection_name (str): The name of the collection to remove the records from. + keys (List[str]): The unique database keys of the records to remove. Returns: None @@ -197,14 +197,14 @@ async def get_nearest_match( ) -> tuple[MemoryRecord, float]: """Gets the nearest match to an embedding using cosine similarity. - Arguments: - collection_name {str} -- The name of the collection to get the nearest match from. - embedding {ndarray} -- The embedding to find the nearest match to. - min_relevance_score {float} -- The minimum relevance score of the match. (default: {0.0}) - with_embedding {bool} -- Whether to include the embedding in the result. (default: {False}) + Args: + collection_name (str): The name of the collection to get the nearest match from. + embedding (ndarray): The embedding to find the nearest match to. + min_relevance_score (float): The minimum relevance score of the match. (default: {0.0}) + with_embedding (bool): Whether to include the embedding in the result. (default: {False}) Returns: - Tuple[MemoryRecord, float] -- The record and the relevance score. + Tuple[MemoryRecord, float]: The record and the relevance score. """ return self.get_nearest_matches( collection_name=collection_name, @@ -224,15 +224,15 @@ async def get_nearest_matches( ) -> list[tuple[MemoryRecord, float]]: """Gets the nearest matches to an embedding using cosine similarity. - Arguments: - collection_name {str} -- The name of the collection to get the nearest matches from. - embedding {ndarray} -- The embedding to find the nearest matches to. - limit {int} -- The maximum number of matches to return. - min_relevance_score {float} -- The minimum relevance score of the matches. (default: {0.0}) - with_embeddings {bool} -- Whether to include the embeddings in the results. (default: {False}) + Args: + collection_name (str): The name of the collection to get the nearest matches from. + embedding (ndarray): The embedding to find the nearest matches to. + limit (int): The maximum number of matches to return. + min_relevance_score (float): The minimum relevance score of the matches. (default: {0.0}) + with_embeddings (bool): Whether to include the embeddings in the results. (default: {False}) Returns: - List[Tuple[MemoryRecord, float]] -- The records and their relevance scores. + List[Tuple[MemoryRecord, float]]: The records and their relevance scores. """ if collection_name not in self._store: logger.warning( @@ -282,12 +282,12 @@ async def get_nearest_matches( def compute_similarity_scores(self, embedding: ndarray, embedding_array: ndarray) -> ndarray: """Computes the cosine similarity scores between a query embedding and a group of embeddings. - Arguments: - embedding {ndarray} -- The query embedding. - embedding_array {ndarray} -- The group of embeddings. + Args: + embedding (ndarray): The query embedding. + embedding_array (ndarray): The group of embeddings. Returns: - ndarray -- The cosine similarity scores. + ndarray: The cosine similarity scores. """ query_norm = linalg.norm(embedding) collection_norm = linalg.norm(embedding_array, axis=1) diff --git a/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner.py b/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner.py index 501cdb5f505a..14fed3505487 100644 --- a/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner.py +++ b/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. - import asyncio import logging import os @@ -59,7 +58,7 @@ class FunctionCallingStepwisePlanner(KernelBaseModel): step_prompt: str def __init__(self, service_id: str, options: FunctionCallingStepwisePlannerOptions | None = None): - """Initialize a new instance of the FunctionCallingStepwisePlanner + """Initialize a new instance of the FunctionCallingStepwisePlanner. The FunctionCallingStepwisePlanner is a planner based on top of an OpenAI Chat Completion service (whether it be AzureOpenAI or OpenAI), so that we can use tools. @@ -94,8 +93,7 @@ async def invoke( arguments: KernelArguments | None = None, **kwargs: Any, ) -> FunctionCallingStepwisePlannerResult: - """ - Execute the function calling stepwise planner + """Execute the function calling stepwise planner. Args: kernel: The kernel instance @@ -226,7 +224,7 @@ async def _build_chat_history_for_step( arguments: KernelArguments, service: OpenAIChatCompletion | AzureChatCompletion, ) -> ChatHistory: - """Build the chat history for the stepwise planner""" + """Build the chat history for the stepwise planner.""" chat_history = ChatHistory() additional_arguments = KernelArguments( goal=goal, @@ -244,8 +242,10 @@ async def _build_chat_history_for_step( def _create_config_from_yaml(self, kernel: Kernel) -> "KernelFunction": """A temporary method to create a function from the yaml file. + The yaml.safe_load will be replaced with the proper kernel - method later.""" + method later. + """ data = yaml.safe_load(self.generate_plan_yaml) prompt_template_config = PromptTemplateConfig(**data) if "default" in prompt_template_config.execution_settings: @@ -264,7 +264,7 @@ async def _generate_plan( kernel: Kernel, arguments: KernelArguments, ) -> str: - """Generate the plan for the given question using the kernel""" + """Generate the plan for the given question using the kernel.""" generate_plan_function = self._create_config_from_yaml(kernel) # TODO: revisit when function call behavior is finalized, and other function calling models are added functions_manual = [ diff --git a/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner_options.py b/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner_options.py index df2beb4244c9..a3244fd3341c 100644 --- a/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner_options.py +++ b/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner_options.py @@ -27,6 +27,7 @@ class FunctionCallingStepwisePlannerOptions(PlannerOptions): @model_validator(mode="before") @classmethod def calculate_token_limits(cls, data: Any) -> Any: + """Calculate the token limits based on the max_tokens and max_tokens_ratio.""" if isinstance(data, dict): max_tokens = data.get("max_tokens") # Ensure max_tokens_ratio has a default value if not provided diff --git a/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner_result.py b/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner_result.py index e9b139dd2f83..8e4df94294e5 100644 --- a/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner_result.py +++ b/python/semantic_kernel/planners/function_calling_stepwise_planner/function_calling_stepwise_planner_result.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. - from typing import Annotated from semantic_kernel.contents.chat_history import ChatHistory @@ -9,7 +8,7 @@ class FunctionCallingStepwisePlannerResult(KernelBaseModel): - """The result of the function calling stepwise planner""" + """The result of the function calling stepwise planner.""" final_answer: str = "" chat_history: ChatHistory | None = None @@ -17,8 +16,9 @@ class FunctionCallingStepwisePlannerResult(KernelBaseModel): class UserInteraction: - """The Kernel Function used to interact with the user""" + """The Kernel Function used to interact with the user.""" @kernel_function(description="The final answer to return to the user", name="SendFinalAnswer") def send_final_answer(self, answer: Annotated[str, "The final answer"]) -> str: + """Send the final answer to the user.""" return "Thanks" diff --git a/python/semantic_kernel/planners/plan.py b/python/semantic_kernel/planners/plan.py index 38c31a2420f6..28cc478b7424 100644 --- a/python/semantic_kernel/planners/plan.py +++ b/python/semantic_kernel/planners/plan.py @@ -38,38 +38,47 @@ class Plan: @property def name(self) -> str: + """Get the name for the plan.""" return self._name @property def state(self) -> KernelArguments: + """Get the state for the plan.""" return self._state @property def steps(self) -> list["Plan"]: + """Get the steps for the plan.""" return self._steps @property def plugin_name(self) -> str: + """Get the plugin name for the plan.""" return self._plugin_name @property def description(self) -> str: + """Get the description for the plan.""" return self._description @property def function(self) -> Callable[..., Any]: + """Get the function for the plan.""" return self._function @property def parameters(self) -> KernelArguments: + """Get the parameters for the plan.""" return self._parameters @property def is_prompt(self) -> bool: + """Check if the plan is a prompt.""" return self._is_prompt @property def is_native(self) -> bool: + """Check if the plan is native code.""" if self._is_prompt is None: return None else: @@ -77,14 +86,17 @@ def is_native(self) -> bool: @property def prompt_execution_settings(self) -> PromptExecutionSettings: + """Get the AI configuration for the plan.""" return self._prompt_execution_settings @property def has_next_step(self) -> bool: + """Check if the plan has a next step.""" return self._next_step_index < len(self._steps) @property def next_step_index(self) -> int: + """Get the next step index.""" return self._next_step_index def __init__( @@ -99,6 +111,7 @@ def __init__( steps: list["Plan"] | None = None, function: KernelFunction | None = None, ) -> None: + """Initializes a new instance of the Plan class.""" self._name = f"plan_{generate_random_ascii_name()}" if name is None else name self._plugin_name = f"p_{generate_random_ascii_name()}" if plugin_name is None else plugin_name self._description = "" if description is None else description @@ -117,10 +130,12 @@ def __init__( @classmethod def from_goal(cls, goal: str) -> "Plan": + """Create a plan from a goal.""" return cls(description=goal, plugin_name=cls.__name__) @classmethod def from_function(cls, function: KernelFunction) -> "Plan": + """Create a plan from a function.""" plan = cls() plan.set_function(function) return plan @@ -129,20 +144,15 @@ async def invoke( self, kernel: Kernel, arguments: KernelArguments | None = None, - # TODO: cancellation_token: CancellationToken, ) -> FunctionResult: - """ - Invoke the plan asynchronously. + """Invoke the plan asynchronously. Args: - input (str, optional): The input to the plan. Defaults to None. + kernel (Kernel): The kernel to use for invocation. arguments (KernelArguments, optional): The context to use. Defaults to None. - settings (PromptExecutionSettings, optional): The AI request settings to use. Defaults to None. - memory (SemanticTextMemoryBase, optional): The memory to use. Defaults to None. - **kwargs: Additional keyword arguments. Returns: - KernelContext: The updated context. + FunctionResult: The result of the function. """ if not arguments: arguments = copy(self._state) @@ -183,10 +193,12 @@ def set_ai_configuration( self, settings: PromptExecutionSettings, ) -> None: + """Set the AI configuration for the plan.""" self._prompt_execution_settings = settings @property def metadata(self) -> KernelFunctionMetadata: + """Get the metadata for the plan.""" if self._function is not None: return self._function.metadata return KernelFunctionMetadata( @@ -198,6 +210,7 @@ def metadata(self) -> KernelFunctionMetadata: ) def set_available_functions(self, plan: "Plan", kernel: "Kernel", arguments: "KernelArguments") -> "Plan": + """Set the available functions for the plan.""" if len(plan.steps) == 0: try: plugin_function = kernel.get_function(plan.plugin_name, plan.name) @@ -214,6 +227,7 @@ def set_available_functions(self, plan: "Plan", kernel: "Kernel", arguments: "Ke return plan def add_steps(self, steps: list["Plan"] | list[KernelFunction]) -> None: + """Add steps to the plan.""" for step in steps: if type(step) is Plan: self._steps.append(step) @@ -232,6 +246,7 @@ def add_steps(self, steps: list["Plan"] | list[KernelFunction]) -> None: self._steps.append(new_step) def set_function(self, function: KernelFunction) -> None: + """Set the function for the plan.""" self._function = function self._name = function.name self._plugin_name = function.plugin_name @@ -245,9 +260,11 @@ async def run_next_step( kernel: Kernel, arguments: KernelArguments, ) -> Optional["FunctionResult"]: + """Run the next step in the plan.""" return await self.invoke_next_step(kernel, arguments) async def invoke_next_step(self, kernel: Kernel, arguments: KernelArguments) -> Optional["FunctionResult"]: + """Invoke the next step in the plan.""" if not self.has_next_step: return None step = self._steps[self._next_step_index] @@ -278,11 +295,13 @@ async def invoke_next_step(self, kernel: Kernel, arguments: KernelArguments) -> return result def add_variables_to_state(self, state: KernelArguments, variables: KernelArguments) -> None: + """Add variables to the state.""" for key in variables.keys(): if key not in state.keys(): state[key] = variables[key] def update_arguments_with_outputs(self, arguments: KernelArguments) -> KernelArguments: + """Update the arguments with the outputs from the current step.""" if Plan.DEFAULT_RESULT_KEY in self._state: result_string = self._state[Plan.DEFAULT_RESULT_KEY] else: @@ -298,6 +317,7 @@ def update_arguments_with_outputs(self, arguments: KernelArguments) -> KernelArg return arguments def get_next_step_arguments(self, arguments: KernelArguments, step: "Plan") -> KernelArguments: + """Get the arguments for the next step.""" # Priority for Input # - Parameters (expand from variables if needed) # - KernelArguments @@ -359,6 +379,7 @@ def get_next_step_arguments(self, arguments: KernelArguments, step: "Plan") -> K return step_arguments def expand_from_arguments(self, arguments: KernelArguments, input_from_step: Any) -> str: + """Expand variables in the input from the step using the arguments.""" result = input_from_step variables_regex = r"\$(?P\w+)" matches = [m for m in re.finditer(variables_regex, str(input_from_step))] diff --git a/python/semantic_kernel/planners/planner_extensions.py b/python/semantic_kernel/planners/planner_extensions.py index f97dafa12d95..69ee19905377 100644 --- a/python/semantic_kernel/planners/planner_extensions.py +++ b/python/semantic_kernel/planners/planner_extensions.py @@ -17,6 +17,7 @@ class PlannerFunctionExtension: @staticmethod def to_manual_string(function: KernelFunctionMetadata): + """Convert the function to a string that can be used in the manual.""" inputs = [ f" - {parameter.name}: {parameter.description}" + (f" (default value: {parameter.default_value})" if parameter.default_value else "") @@ -27,6 +28,7 @@ def to_manual_string(function: KernelFunctionMetadata): @staticmethod def to_embedding_string(function: KernelFunctionMetadata): + """Convert the function to a string that can be used as an embedding.""" inputs = "\n".join([f" - {parameter.name}: {parameter.description}" for parameter in function.parameters]) return f"{function.name}:\n description: {function.description}\n " f" inputs:\n{inputs}" @@ -41,6 +43,7 @@ async def get_functions_manual( arguments: KernelArguments, options: PlannerOptions = None, ) -> str: + """Get the string of the function.""" options = options or PlannerOptions() if options.get_available_functions is None: @@ -56,6 +59,7 @@ async def get_available_functions( arguments: KernelArguments, options: PlannerOptions, ): + """Get the available functions for the kernel.""" excluded_plugins = options.excluded_plugins or [] excluded_functions = options.excluded_functions or [] diff --git a/python/semantic_kernel/planners/planner_options.py b/python/semantic_kernel/planners/planner_options.py index f79d24d8062b..94e79f53ea46 100644 --- a/python/semantic_kernel/planners/planner_options.py +++ b/python/semantic_kernel/planners/planner_options.py @@ -7,7 +7,7 @@ class PlannerOptions(KernelBaseModel): - """The default planner options that planners inherit from""" + """The default planner options that planners inherit from.""" excluded_plugins: set[str] = set() excluded_functions: set[str] = set() diff --git a/python/semantic_kernel/planners/sequential_planner/sequential_planner.py b/python/semantic_kernel/planners/sequential_planner/sequential_planner.py index 8ebfc3d11dc8..9cad4927f5f9 100644 --- a/python/semantic_kernel/planners/sequential_planner/sequential_planner.py +++ b/python/semantic_kernel/planners/sequential_planner/sequential_planner.py @@ -26,6 +26,7 @@ def read_file(file_path: str) -> str: + """Reads the content of a file.""" with open(file_path) as file: return file.read() @@ -45,8 +46,7 @@ def __init__( config: SequentialPlannerConfig = None, prompt: str = None, ) -> None: - """ - Initializes a new instance of the SequentialPlanner class. + """Initializes a new instance of the SequentialPlanner class. Args: kernel (Kernel): The kernel instance to use for planning @@ -54,7 +54,6 @@ def __init__( config (SequentialPlannerConfig, optional): The configuration to use for planning. Defaults to None. prompt (str, optional): The prompt to use for planning. Defaults to None. """ - assert isinstance(kernel, Kernel) self.config = config or SequentialPlannerConfig() self.config.excluded_plugins.append(self.RESTRICTED_PLUGIN_NAME) @@ -90,6 +89,7 @@ def _init_flow_function(self, prompt: str, service_id: str) -> "KernelFunction": ) async def create_plan(self, goal: str) -> Plan: + """Create a plan for the specified goal.""" if len(goal) == 0: raise PlannerInvalidGoalError("The goal specified is empty") diff --git a/python/semantic_kernel/planners/sequential_planner/sequential_planner_config.py b/python/semantic_kernel/planners/sequential_planner/sequential_planner_config.py index ad53723480f4..939755c2b97a 100644 --- a/python/semantic_kernel/planners/sequential_planner/sequential_planner_config.py +++ b/python/semantic_kernel/planners/sequential_planner/sequential_planner_config.py @@ -16,6 +16,7 @@ def __init__( get_available_functions: Callable = None, get_plugin_function: Callable = None, ): + """Initializes a new instance of the SequentialPlannerConfig class.""" self.relevancy_threshold: float = relevancy_threshold self.max_relevant_functions: int = max_relevant_functions self.excluded_plugins: list[str] = excluded_plugins or [] diff --git a/python/semantic_kernel/planners/sequential_planner/sequential_planner_extensions.py b/python/semantic_kernel/planners/sequential_planner/sequential_planner_extensions.py index 3a7ba1f7278e..0a1175f27512 100644 --- a/python/semantic_kernel/planners/sequential_planner/sequential_planner_extensions.py +++ b/python/semantic_kernel/planners/sequential_planner/sequential_planner_extensions.py @@ -14,6 +14,7 @@ class SequentialPlannerFunctionExtension: @staticmethod def to_manual_string(function: KernelFunctionMetadata): + """Convert the function to a manual string.""" inputs = [ f" - {parameter.name}: {parameter.description}" + (f" (default value: {parameter.default_value})" if parameter.default_value else "") @@ -24,6 +25,7 @@ def to_manual_string(function: KernelFunctionMetadata): @staticmethod def to_embedding_string(function: KernelFunctionMetadata): + """Convert the function to an embedding string.""" inputs = "\n".join([f" - {parameter.name}: {parameter.description}" for parameter in function.parameters]) return f"{function.name}:\n description: {function.description}\n " f" inputs:\n{inputs}" @@ -39,6 +41,7 @@ async def get_functions_manual( semantic_query: str = None, config: SequentialPlannerConfig = None, ) -> str: + """Get the functions manual.""" config = config or SequentialPlannerConfig() if config.get_available_functions is None: @@ -57,6 +60,7 @@ async def get_available_functions( config: SequentialPlannerConfig, semantic_query: str | None = None, ): + """Get the available functions based on the semantic query.""" excluded_plugins = config.excluded_plugins or [] excluded_functions = config.excluded_functions or [] included_functions = config.included_functions or [] @@ -93,6 +97,7 @@ async def get_relevant_functions( available_functions: list[KernelFunctionMetadata], memories: list[MemoryQueryResult] | None = None, ) -> list[KernelFunctionMetadata]: + """Get relevant functions from the memories.""" relevant_functions = [] # TODO: cancellation if memories is None: diff --git a/python/semantic_kernel/planners/sequential_planner/sequential_planner_parser.py b/python/semantic_kernel/planners/sequential_planner/sequential_planner_parser.py index 96c6cf805e5f..0c844dd25e09 100644 --- a/python/semantic_kernel/planners/sequential_planner/sequential_planner_parser.py +++ b/python/semantic_kernel/planners/sequential_planner/sequential_planner_parser.py @@ -29,6 +29,7 @@ def to_plan_from_xml( get_plugin_function: Callable[[str, str], KernelFunction | None] | None = None, allow_missing_functions: bool = False, ): + """Convert an xml string to a plan.""" xml_string = "" + xml_string + "" try: xml_doc = ET.fromstring(xml_string) @@ -112,6 +113,7 @@ def to_plan_from_xml( @staticmethod def get_plugin_function_names(plugin_function_name: str) -> tuple[str, str]: + """Get the plugin and function names from the plugin function name.""" plugin_function_name_parts = plugin_function_name.split("-") plugin_name = plugin_function_name_parts[0] if len(plugin_function_name_parts) > 0 else "" function_name = plugin_function_name_parts[1] if len(plugin_function_name_parts) > 1 else plugin_function_name diff --git a/python/semantic_kernel/prompt_template/handlebars_prompt_template.py b/python/semantic_kernel/prompt_template/handlebars_prompt_template.py index 8fac48c480b1..fc34284c6aab 100644 --- a/python/semantic_kernel/prompt_template/handlebars_prompt_template.py +++ b/python/semantic_kernel/prompt_template/handlebars_prompt_template.py @@ -45,11 +45,13 @@ class HandlebarsPromptTemplate(PromptTemplateBase): @field_validator("prompt_template_config") @classmethod def validate_template_format(cls, v: "PromptTemplateConfig") -> "PromptTemplateConfig": + """Validate the template format.""" if v.template_format != HANDLEBARS_TEMPLATE_FORMAT_NAME: raise ValueError(f"Invalid prompt template format: {v.template_format}. Expected: handlebars") return v def model_post_init(self, __context: Any) -> None: + """Post init model.""" if not self.prompt_template_config.template: self._template_compiler = None return @@ -62,7 +64,8 @@ def model_post_init(self, __context: Any) -> None: ) from e async def render(self, kernel: "Kernel", arguments: Optional["KernelArguments"] = None) -> str: - """ + """Render the prompt template. + Using the prompt template, replace the variables with their values and execute the functions replacing their reference with the function result. diff --git a/python/semantic_kernel/prompt_template/jinja2_prompt_template.py b/python/semantic_kernel/prompt_template/jinja2_prompt_template.py index 18645b218251..126b9043df23 100644 --- a/python/semantic_kernel/prompt_template/jinja2_prompt_template.py +++ b/python/semantic_kernel/prompt_template/jinja2_prompt_template.py @@ -22,8 +22,7 @@ class Jinja2PromptTemplate(PromptTemplateBase): - """ - Creates and renders Jinja2 prompt templates to text. + """Creates and renders Jinja2 prompt templates to text. Jinja2 templates support advanced features such as variable substitution, control structures, and inheritance, making it possible to dynamically generate text based on input arguments @@ -53,18 +52,21 @@ class Jinja2PromptTemplate(PromptTemplateBase): @field_validator("prompt_template_config") @classmethod def validate_template_format(cls, v: "PromptTemplateConfig") -> "PromptTemplateConfig": + """Validate the template format.""" if v.template_format != JINJA2_TEMPLATE_FORMAT_NAME: raise ValueError(f"Invalid prompt template format: {v.template_format}. Expected: jinja2") return v def model_post_init(self, _: Any) -> None: + """Post init model.""" if not self.prompt_template_config.template: self._env = None return self._env = ImmutableSandboxedEnvironment(loader=BaseLoader()) async def render(self, kernel: "Kernel", arguments: Optional["KernelArguments"] = None) -> str: - """ + """Render the prompt template. + Using the prompt template, replace the variables with their values and execute the functions replacing their reference with the function result. diff --git a/python/semantic_kernel/prompt_template/kernel_prompt_template.py b/python/semantic_kernel/prompt_template/kernel_prompt_template.py index 2a3f0268cce9..a530d4cd2858 100644 --- a/python/semantic_kernel/prompt_template/kernel_prompt_template.py +++ b/python/semantic_kernel/prompt_template/kernel_prompt_template.py @@ -25,7 +25,7 @@ class KernelPromptTemplate(PromptTemplateBase): """Create a Kernel prompt template. - Arguments: + Args: prompt_template_config (PromptTemplateConfig): The prompt template configuration This includes the actual template to use. allow_dangerously_set_content (bool = False): Allow content without encoding throughout, this overrides @@ -42,11 +42,13 @@ class KernelPromptTemplate(PromptTemplateBase): @field_validator("prompt_template_config") @classmethod def validate_template_format(cls, v: "PromptTemplateConfig") -> "PromptTemplateConfig": + """Validate the template format.""" if v.template_format != KERNEL_TEMPLATE_FORMAT_NAME: raise ValueError(f"Invalid prompt template format: {v.template_format}. Expected: semantic-kernel") return v def model_post_init(self, __context: Any) -> None: + """Post init model.""" self._blocks = self.extract_blocks() # Add all of the existing input variables to our known set. We'll avoid adding any # dynamically discovered input variables with the same name. @@ -78,12 +80,7 @@ def _add_if_missing(self, variable_name: str, seen: set | None = None): self.prompt_template_config.input_variables.append(InputVariable(name=variable_name)) def extract_blocks(self) -> list[Block]: - """ - Given a prompt template string, extract all the blocks - (text, variables, function calls). - - Args: - template_text: Prompt template + """Given the prompt template, extract all the blocks (text, variables, function calls). Returns: A list of all the blocks, ie the template tokenized in @@ -95,7 +92,8 @@ def extract_blocks(self) -> list[Block]: return TemplateTokenizer.tokenize(self.prompt_template_config.template) async def render(self, kernel: "Kernel", arguments: Optional["KernelArguments"] = None) -> str: - """ + """Render the prompt template. + Using the prompt template, replace the variables with their values and execute the functions replacing their reference with the function result. @@ -112,8 +110,7 @@ async def render(self, kernel: "Kernel", arguments: Optional["KernelArguments"] return await self.render_blocks(self._blocks, kernel, arguments) async def render_blocks(self, blocks: list[Block], kernel: "Kernel", arguments: "KernelArguments") -> str: - """ - Given a list of blocks render each block and compose the final result. + """Given a list of blocks render each block and compose the final result. :param blocks: Template blocks generated by ExtractBlocks :param context: Access into the current kernel execution context diff --git a/python/semantic_kernel/prompt_template/prompt_template_base.py b/python/semantic_kernel/prompt_template/prompt_template_base.py index 3ff111055c2b..c293846175d9 100644 --- a/python/semantic_kernel/prompt_template/prompt_template_base.py +++ b/python/semantic_kernel/prompt_template/prompt_template_base.py @@ -19,6 +19,7 @@ class PromptTemplateBase(KernelBaseModel, ABC): @abstractmethod async def render(self, kernel: "Kernel", arguments: "KernelArguments") -> str: + """Render the prompt template.""" pass def _get_trusted_arguments( @@ -60,8 +61,7 @@ def _get_allow_unsafe_function_output(self) -> bool: return allow_unsafe_function_output def _should_escape(self, name: str, input_variables: list["InputVariable"]) -> bool: - """ - Check if the variable should be escaped. + """Check if the variable should be escaped. If the PromptTemplate allows dangerously set content, then the variable will not be escaped, even if the input_variables does specify this. diff --git a/python/semantic_kernel/prompt_template/prompt_template_config.py b/python/semantic_kernel/prompt_template/prompt_template_config.py index 7d1f2c0b4cd2..da79603f2f00 100644 --- a/python/semantic_kernel/prompt_template/prompt_template_config.py +++ b/python/semantic_kernel/prompt_template/prompt_template_config.py @@ -40,7 +40,7 @@ class PromptTemplateConfig(KernelBaseModel): @model_validator(mode="after") def check_input_variables(self): - """Verify that input variable default values are string only""" + """Verify that input variable default values are string only.""" for variable in self.input_variables: if variable.default and not isinstance(variable.default, str): raise TypeError(f"Default value for input variable {variable.name} must be a string.") @@ -111,8 +111,10 @@ def restore( name: The name of the prompt template. description: The description of the prompt template. template: The template for the prompt. + template_format: The format of the template, should be 'semantic-kernel', 'jinja2' or 'handlebars'. input_variables: The input variables for the prompt. execution_settings: The execution settings for the prompt. + allow_dangerously_set_content: Allow content without encoding. Returns: A new PromptTemplateConfig instance. diff --git a/python/semantic_kernel/reliability/pass_through_without_retry.py b/python/semantic_kernel/reliability/pass_through_without_retry.py index 95f6c1199fe7..7fe68370c426 100644 --- a/python/semantic_kernel/reliability/pass_through_without_retry.py +++ b/python/semantic_kernel/reliability/pass_through_without_retry.py @@ -18,11 +18,11 @@ class PassThroughWithoutRetry(RetryMechanismBase, KernelBaseModel): async def execute_with_retry(self, action: Callable[[], Awaitable[T]]) -> Awaitable[T]: """Executes the given action with retry logic. - Arguments: - action {Callable[[], Awaitable[T]]} -- The action to retry on exception. + Args: + action (Callable[[], Awaitable[T]]): The action to retry on exception. Returns: - Awaitable[T] -- An awaitable that will return the result of the action. + Awaitable[T]: An awaitable that will return the result of the action. """ try: await action() diff --git a/python/semantic_kernel/reliability/retry_mechanism_base.py b/python/semantic_kernel/reliability/retry_mechanism_base.py index d57298ccc8b9..bc026e0c5235 100644 --- a/python/semantic_kernel/reliability/retry_mechanism_base.py +++ b/python/semantic_kernel/reliability/retry_mechanism_base.py @@ -15,10 +15,10 @@ class RetryMechanismBase(ABC): async def execute_with_retry(self, action: Callable[[], Awaitable[T]]) -> Awaitable[T]: """Executes the given action with retry logic. - Arguments: - action {Callable[[], Awaitable[T]]} -- The action to retry on exception. + Args: + action (Callable[[], Awaitable[T]]): The action to retry on exception. Returns: - Awaitable[T] -- An awaitable that will return the result of the action. + Awaitable[T]: An awaitable that will return the result of the action. """ pass diff --git a/python/semantic_kernel/schema/kernel_json_schema_builder.py b/python/semantic_kernel/schema/kernel_json_schema_builder.py index 92f8f99e4b3a..34649c8a361f 100644 --- a/python/semantic_kernel/schema/kernel_json_schema_builder.py +++ b/python/semantic_kernel/schema/kernel_json_schema_builder.py @@ -27,7 +27,6 @@ class KernelJsonSchemaBuilder: @classmethod def build(cls, parameter_type: type | str, description: str | None = None) -> dict[str, Any]: """Builds JSON schema for a given parameter type.""" - if isinstance(parameter_type, str): return cls.build_from_type_name(parameter_type, description) if issubclass(parameter_type, KernelBaseModel): diff --git a/python/semantic_kernel/services/ai_service_selector.py b/python/semantic_kernel/services/ai_service_selector.py index 4f053ff9f09a..eb47e29a7411 100644 --- a/python/semantic_kernel/services/ai_service_selector.py +++ b/python/semantic_kernel/services/ai_service_selector.py @@ -25,8 +25,9 @@ def select_ai_service( arguments: "KernelArguments", type_: type["AI_SERVICE_CLIENT_TYPE"] | None = None, ) -> tuple["AI_SERVICE_CLIENT_TYPE", "PromptExecutionSettings"]: - """Select an AI Service on a first come, first served basis, - starting with execution settings in the arguments, + """Select an AI Service on a first come, first served basis. + + Starts with execution settings in the arguments, followed by the execution settings from the function. If the same service_id is in both, the one in the arguments will be used. """ diff --git a/python/semantic_kernel/services/kernel_services_extension.py b/python/semantic_kernel/services/kernel_services_extension.py index 560e39d86659..6e069ea9a5ee 100644 --- a/python/semantic_kernel/services/kernel_services_extension.py +++ b/python/semantic_kernel/services/kernel_services_extension.py @@ -107,6 +107,7 @@ def get_service( return service def get_services_by_type(self, type: type[ALL_SERVICE_TYPES]) -> dict[str, ALL_SERVICE_TYPES]: + """Get all services of a specific type.""" return {service.service_id: service for service in self.services.values() if isinstance(service, type)} # type: ignore def get_prompt_execution_settings_from_service_id( @@ -120,6 +121,12 @@ def get_prompt_execution_settings_from_service_id( ) def add_service(self, service: AIServiceClientBase, overwrite: bool = False) -> None: + """Add a single service to the Kernel. + + Args: + service (AIServiceClientBase): The service to add. + overwrite (bool, optional): Whether to overwrite the service if it already exists. Defaults to False. + """ if service.service_id not in self.services or overwrite: self.services[service.service_id] = service else: diff --git a/python/semantic_kernel/template_engine/blocks/block.py b/python/semantic_kernel/template_engine/blocks/block.py index 1657fe7534cf..25539ea538f1 100644 --- a/python/semantic_kernel/template_engine/blocks/block.py +++ b/python/semantic_kernel/template_engine/blocks/block.py @@ -18,4 +18,5 @@ class Block(KernelBaseModel): @field_validator("content", mode="before") @classmethod def content_strip(cls, content: str): + """Strip the content of the block.""" return content.strip() diff --git a/python/semantic_kernel/template_engine/blocks/function_id_block.py b/python/semantic_kernel/template_engine/blocks/function_id_block.py index 954bfa8454fb..b8f4e7f37667 100644 --- a/python/semantic_kernel/template_engine/blocks/function_id_block.py +++ b/python/semantic_kernel/template_engine/blocks/function_id_block.py @@ -62,4 +62,5 @@ def parse_content(cls, fields: dict[str, Any]) -> dict[str, Any]: return fields def render(self, *_: tuple["Kernel", Optional["KernelArguments"]]) -> str: + """Render the function id block.""" return self.content diff --git a/python/semantic_kernel/template_engine/blocks/named_arg_block.py b/python/semantic_kernel/template_engine/blocks/named_arg_block.py index 31729feca607..140960b2eda9 100644 --- a/python/semantic_kernel/template_engine/blocks/named_arg_block.py +++ b/python/semantic_kernel/template_engine/blocks/named_arg_block.py @@ -88,6 +88,7 @@ def parse_content(cls, fields: Any) -> Any: return fields def render(self, kernel: "Kernel", arguments: Optional["KernelArguments"] = None) -> Any: + """Render the named argument block.""" if self.value: return self.value.render(kernel, arguments) if arguments is None: diff --git a/python/semantic_kernel/template_engine/blocks/text_block.py b/python/semantic_kernel/template_engine/blocks/text_block.py index 20bd2cbb8b6f..ad9dd6b05c71 100644 --- a/python/semantic_kernel/template_engine/blocks/text_block.py +++ b/python/semantic_kernel/template_engine/blocks/text_block.py @@ -21,7 +21,10 @@ class TextBlock(Block): @field_validator("content", mode="before") @classmethod def content_strip(cls, content: str): - # overload strip method text blocks are not stripped. + """Strip the content of the text block. + + Overload strip method, text blocks are not stripped. + """ return content @classmethod @@ -31,6 +34,7 @@ def from_text( start_index: int | None = None, stop_index: int | None = None, ): + """Create a text block from a string.""" if text is None: return cls(content="") if start_index is not None and stop_index is not None: @@ -49,4 +53,5 @@ def from_text( return cls(content=text) def render(self, *_: tuple[Optional["Kernel"], Optional["KernelArguments"]]) -> str: + """Render the text block.""" return self.content diff --git a/python/semantic_kernel/template_engine/blocks/val_block.py b/python/semantic_kernel/template_engine/blocks/val_block.py index 067b31f88128..e1e5c88926a4 100644 --- a/python/semantic_kernel/template_engine/blocks/val_block.py +++ b/python/semantic_kernel/template_engine/blocks/val_block.py @@ -70,4 +70,5 @@ def parse_content(cls, fields: Any) -> Any: return fields def render(self, *_: tuple["Kernel", Optional["KernelArguments"]]) -> str: + """Render the value block.""" return self.value diff --git a/python/semantic_kernel/template_engine/blocks/var_block.py b/python/semantic_kernel/template_engine/blocks/var_block.py index e66f815cd5df..93ac23e14770 100644 --- a/python/semantic_kernel/template_engine/blocks/var_block.py +++ b/python/semantic_kernel/template_engine/blocks/var_block.py @@ -67,7 +67,9 @@ def parse_content(cls, fields: Any) -> Any: def render(self, _: "Kernel", arguments: Optional["KernelArguments"] = None) -> str: """Render the variable block with the given arguments. - If the variable is not found in the arguments, return an empty string.""" + + If the variable is not found in the arguments, return an empty string. + """ if arguments is None: return "" value = arguments.get(self.name, None) diff --git a/python/semantic_kernel/template_engine/code_tokenizer.py b/python/semantic_kernel/template_engine/code_tokenizer.py index c63b91fdda6d..fc494feffd78 100644 --- a/python/semantic_kernel/template_engine/code_tokenizer.py +++ b/python/semantic_kernel/template_engine/code_tokenizer.py @@ -25,6 +25,7 @@ class CodeTokenizer: @staticmethod def tokenize(text: str) -> list[Block]: + """Tokenize the code text into blocks.""" # Remove spaces, which are ignored anyway text = text.strip() if text else "" # Render None/empty to [] diff --git a/python/semantic_kernel/template_engine/protocols/code_renderer.py b/python/semantic_kernel/template_engine/protocols/code_renderer.py index 52ec84d9372e..f88d7d74571e 100644 --- a/python/semantic_kernel/template_engine/protocols/code_renderer.py +++ b/python/semantic_kernel/template_engine/protocols/code_renderer.py @@ -9,13 +9,10 @@ @runtime_checkable class CodeRenderer(Protocol): - """ - Protocol for dynamic code blocks that need async IO to be rendered. - """ + """Protocol for dynamic code blocks that need async IO to be rendered.""" async def render_code(self, kernel: "Kernel", arguments: "KernelArguments") -> str: - """ - Render the block using the given context. + """Render the block using the given context. :param context: kernel execution context :return: Rendered content diff --git a/python/semantic_kernel/template_engine/protocols/text_renderer.py b/python/semantic_kernel/template_engine/protocols/text_renderer.py index d9db5df2e61b..5c9e94e3c1a3 100644 --- a/python/semantic_kernel/template_engine/protocols/text_renderer.py +++ b/python/semantic_kernel/template_engine/protocols/text_renderer.py @@ -9,13 +9,10 @@ @runtime_checkable class TextRenderer(Protocol): - """ - Protocol for static (text) blocks that don't need async rendering. - """ + """Protocol for static (text) blocks that don't need async rendering.""" def render(self, kernel: "Kernel", arguments: Optional["KernelArguments"] = None) -> str: - """ - Render the block using only the given variables. + """Render the block using only the given variables. :param variables: Optional variables used to render the block :return: Rendered content diff --git a/python/semantic_kernel/template_engine/template_tokenizer.py b/python/semantic_kernel/template_engine/template_tokenizer.py index 2b0c8c59df99..c37c23865a74 100644 --- a/python/semantic_kernel/template_engine/template_tokenizer.py +++ b/python/semantic_kernel/template_engine/template_tokenizer.py @@ -2,11 +2,7 @@ import logging -from semantic_kernel.exceptions import ( - BlockSyntaxError, - CodeBlockTokenError, - TemplateSyntaxError, -) +from semantic_kernel.exceptions import BlockSyntaxError, CodeBlockTokenError, TemplateSyntaxError from semantic_kernel.template_engine.blocks.block import Block from semantic_kernel.template_engine.blocks.block_types import BlockTypes from semantic_kernel.template_engine.blocks.code_block import CodeBlock @@ -28,6 +24,7 @@ class TemplateTokenizer: @staticmethod def tokenize(text: str) -> list[Block]: + """Tokenize the template text into blocks.""" code_tokenizer = CodeTokenizer() # An empty block consists of 4 chars: "{{}}" EMPTY_CODE_BLOCK_LENGTH = 4 diff --git a/python/semantic_kernel/text/function_extension.py b/python/semantic_kernel/text/function_extension.py index d5ee00923b0d..75178fd3fbc5 100644 --- a/python/semantic_kernel/text/function_extension.py +++ b/python/semantic_kernel/text/function_extension.py @@ -9,9 +9,7 @@ async def aggregate_chunked_results( func: KernelFunction, chunked_results: list[str], kernel: Kernel, arguments: KernelArguments ) -> str: - """ - Aggregate the results from the chunked results. - """ + """Aggregate the results from the chunked results.""" results = [] for chunk in chunked_results: arguments["input"] = chunk diff --git a/python/semantic_kernel/text/text_chunker.py b/python/semantic_kernel/text/text_chunker.py index 052d0393facb..2cdfcba6a54d 100644 --- a/python/semantic_kernel/text/text_chunker.py +++ b/python/semantic_kernel/text/text_chunker.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. -""" +"""A Text splitter. + Split text in chunks, attempting to leave meaning intact. For plain text, split looking at new lines first, then periods, and so on. For markdown, split looking at punctuation first, and so on. @@ -39,8 +40,7 @@ def _token_counter(text: str) -> int: - """ - Count the number of tokens in a string. + """Count the number of tokens in a string. TODO: chunking methods should be configurable to allow for different tokenization strategies depending on the model to be called. @@ -50,8 +50,8 @@ def _token_counter(text: str) -> int: def split_plaintext_lines(text: str, max_token_per_line: int, token_counter: Callable = _token_counter) -> list[str]: - """ - Split plain text into lines. + """Split plain text into lines. + it will split on new lines first, and then on punctuation. """ return _split_text_lines( @@ -63,8 +63,8 @@ def split_plaintext_lines(text: str, max_token_per_line: int, token_counter: Cal def split_markdown_lines(text: str, max_token_per_line: int, token_counter: Callable = _token_counter) -> list[str]: - """ - Split markdown into lines. + """Split markdown into lines. + It will split on punctuation first, and then on space and new lines. """ return _split_markdown_lines( @@ -76,10 +76,7 @@ def split_markdown_lines(text: str, max_token_per_line: int, token_counter: Call def split_plaintext_paragraph(text: list[str], max_tokens: int, token_counter: Callable = _token_counter) -> list[str]: - """ - Split plain text into paragraphs. - """ - + """Split plain text into paragraphs.""" split_lines = [] for line in text: split_lines.extend( @@ -95,9 +92,7 @@ def split_plaintext_paragraph(text: list[str], max_tokens: int, token_counter: C def split_markdown_paragraph(text: list[str], max_tokens: int, token_counter: Callable = _token_counter) -> list[str]: - """ - Split markdown into paragraphs. - """ + """Split markdown into paragraphs.""" split_lines = [] for line in text: split_lines.extend( @@ -113,9 +108,7 @@ def split_markdown_paragraph(text: list[str], max_tokens: int, token_counter: Ca def _split_text_paragraph(text: list[str], max_tokens: int, token_counter: Callable = _token_counter) -> list[str]: - """ - Split text into paragraphs. - """ + """Split text into paragraphs.""" if not text: return [] @@ -165,10 +158,7 @@ def _split_markdown_lines( trim: bool, token_counter: Callable = _token_counter, ) -> list[str]: - """ - Split markdown into lines. - """ - + """Split markdown into lines.""" return _split_str_lines( text=text, max_tokens=max_token_per_line, @@ -184,10 +174,7 @@ def _split_text_lines( trim: bool, token_counter: Callable = _token_counter, ) -> list[str]: - """ - Split text into lines. - """ - + """Split text into lines.""" return _split_str_lines( text=text, max_tokens=max_token_per_line, @@ -204,6 +191,7 @@ def _split_str_lines( trim: bool, token_counter: Callable = _token_counter, ) -> list[str]: + """Split text into lines.""" if not text: return [] @@ -240,9 +228,7 @@ def _split_str( trim: bool, token_counter: Callable = _token_counter, ) -> tuple[list[str], bool]: - """ - Split text into lines. - """ + """Split text into lines.""" input_was_split = False if not text: return [], input_was_split # pragma: no cover @@ -301,9 +287,7 @@ def _split_list( trim: bool, token_counter: Callable = _token_counter, ) -> tuple[list[str], bool]: - """ - Split list of string into lines. - """ + """Split list of string into lines.""" if not text: return [], False # pragma: no cover diff --git a/python/semantic_kernel/utils/experimental_decorator.py b/python/semantic_kernel/utils/experimental_decorator.py index 4d8d09eae472..ffd6c136d16c 100644 --- a/python/semantic_kernel/utils/experimental_decorator.py +++ b/python/semantic_kernel/utils/experimental_decorator.py @@ -5,6 +5,7 @@ def experimental_function(func: Callable) -> Callable: + """Decorator to mark a function as experimental.""" if isinstance(func, types.FunctionType): if func.__doc__: func.__doc__ += "\n\nNote: This function is experimental and may change in the future." @@ -17,6 +18,7 @@ def experimental_function(func: Callable) -> Callable: def experimental_class(cls: type) -> type: + """Decorator to mark a class as experimental.""" if isinstance(cls, type): if cls.__doc__: cls.__doc__ += "\n\nNote: This class is experimental and may change in the future." diff --git a/python/semantic_kernel/utils/logging.py b/python/semantic_kernel/utils/logging.py index 3a171572a2f9..86adf5249e52 100644 --- a/python/semantic_kernel/utils/logging.py +++ b/python/semantic_kernel/utils/logging.py @@ -4,7 +4,7 @@ def setup_logging(): - # Setup a detailed logging format. + """Setup a detailed logging format.""" logging.basicConfig( format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S", diff --git a/python/semantic_kernel/utils/naming.py b/python/semantic_kernel/utils/naming.py index 2ed869392d16..2345735f3c92 100644 --- a/python/semantic_kernel/utils/naming.py +++ b/python/semantic_kernel/utils/naming.py @@ -5,8 +5,8 @@ def generate_random_ascii_name(length: int = 16) -> str: - """ - Generate a series of random ASCII characters of the specified length. + """Generate a series of random ASCII characters of the specified length. + As example, plugin/function names can contain upper/lowercase letters, and underscores Args: @@ -16,4 +16,4 @@ def generate_random_ascii_name(length: int = 16) -> str: A string of random ASCII characters of the specified length. """ letters = string.ascii_letters - return "".join(random.choices(letters, k=length)) + return "".join(random.choices(letters, k=length)) # nosec diff --git a/python/setup_dev.sh b/python/setup_dev.sh new file mode 100644 index 000000000000..98a642d3953b --- /dev/null +++ b/python/setup_dev.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# this assumes Poetry is installed and in the Path, see https://python-poetry.org/docs/#installing-with-the-official-installer +# on macos run with `source ./setup_dev.sh` +poetry install +poetry run pre-commit install +poetry run pre-commit autoupdate diff --git a/python/tests/assets/test_native_plugins/TestNativePlugin/custom_class.py b/python/tests/assets/test_native_plugins/TestNativePlugin/custom_class.py index 2dd9cdfcdc02..221d7f7206a4 100644 --- a/python/tests/assets/test_native_plugins/TestNativePlugin/custom_class.py +++ b/python/tests/assets/test_native_plugins/TestNativePlugin/custom_class.py @@ -4,17 +4,14 @@ class TestNativeEchoBotPlugin: - """ - Description: Test Native Plugin for testing purposes - """ + """Description: Test Native Plugin for testing purposes""" @kernel_function( description="Echo for input text", name="echoAsync", ) async def echo(self, text: Annotated[str, "The text to echo"]) -> str: - """ - Echo for input text + """Echo for input text Example: "hello world" => "hello world" diff --git a/python/tests/assets/test_native_plugins/TestNativePluginArgs/class_args.py b/python/tests/assets/test_native_plugins/TestNativePluginArgs/class_args.py index 38ffb70f1e18..d97c5ebc1ed7 100644 --- a/python/tests/assets/test_native_plugins/TestNativePluginArgs/class_args.py +++ b/python/tests/assets/test_native_plugins/TestNativePluginArgs/class_args.py @@ -4,9 +4,7 @@ class TestNativeEchoBotPlugin: - """ - Description: Test Native Plugin for testing purposes - """ + """Description: Test Native Plugin for testing purposes""" def __init__(self, static_input: str | None = None): self.static_input = static_input or "" @@ -16,8 +14,7 @@ def __init__(self, static_input: str | None = None): name="echo", ) def echo(self, text: Annotated[str, "The text to echo"]) -> str: - """ - Echo for input text with a static input + """Echo for input text with a static input Example: "hello world" => "hello world" diff --git a/python/tests/assets/test_native_plugins/TestNativePluginNoClass/native_function.py b/python/tests/assets/test_native_plugins/TestNativePluginNoClass/native_function.py index 57040fa5591e..0102facf1aaf 100644 --- a/python/tests/assets/test_native_plugins/TestNativePluginNoClass/native_function.py +++ b/python/tests/assets/test_native_plugins/TestNativePluginNoClass/native_function.py @@ -8,8 +8,7 @@ name="echoAsync", ) async def echo(text: Annotated[str, "The text to echo"]) -> str: - """ - Echo for input text + """Echo for input text Example: "hello world" => "hello world" diff --git a/python/tests/assets/test_plugins/TestMixedPlugin/native_function.py b/python/tests/assets/test_plugins/TestMixedPlugin/native_function.py index 2dd9cdfcdc02..221d7f7206a4 100644 --- a/python/tests/assets/test_plugins/TestMixedPlugin/native_function.py +++ b/python/tests/assets/test_plugins/TestMixedPlugin/native_function.py @@ -4,17 +4,14 @@ class TestNativeEchoBotPlugin: - """ - Description: Test Native Plugin for testing purposes - """ + """Description: Test Native Plugin for testing purposes""" @kernel_function( description="Echo for input text", name="echoAsync", ) async def echo(self, text: Annotated[str, "The text to echo"]) -> str: - """ - Echo for input text + """Echo for input text Example: "hello world" => "hello world" diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 08aa09c57a76..60c9321349fa 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -155,12 +155,12 @@ def enable_debug_mode(): 3. If you want a trace of a particular functions calls, just add `ss()` as the first line of the function. - NOTE: + Note: ---- It's completely fine to leave `autouse=True` in the fixture. It doesn't affect the tests unless you use `pr` or `ss` in any test. - NOTE: + Note: ---- When you use `ss` or `pr` in a test, pylance or mypy will complain. This is because they don't know that we're adding these functions to the builtins. The diff --git a/python/tests/integration/connectors/memory/test_usearch.py b/python/tests/integration/connectors/memory/test_usearch.py index 7328be389ef7..5c18415f6f95 100644 --- a/python/tests/integration/connectors/memory/test_usearch.py +++ b/python/tests/integration/connectors/memory/test_usearch.py @@ -107,7 +107,6 @@ def gen_memory_records(count: int, ndim: int, start_index: int = 0) -> list[Memo def compare_memory_records(record1: MemoryRecord, record2: MemoryRecord, with_embedding: bool): """Compare two MemoryRecord instances and assert they are the same.""" - assert record1._key == record2._key, f"_key mismatch: {record1._key} != {record2._key}" assert ( record1._timestamp == record2._timestamp diff --git a/python/tests/integration/cross_language/test_cross_language.py b/python/tests/integration/cross_language/test_cross_language.py index bea87dbec342..4a79bd99e75d 100644 --- a/python/tests/integration/cross_language/test_cross_language.py +++ b/python/tests/integration/cross_language/test_cross_language.py @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft. All rights reserved. + import datetime import json import logging @@ -212,7 +214,7 @@ async def test_prompt_with_chat_roles(is_inline, is_streaming, template_format, assert obtained_object is not None data_directory = os.path.join(os.path.dirname(__file__), "data", "prompt_with_chat_roles_expected.json") - with open(data_directory, "r") as f: + with open(data_directory) as f: expected = f.read() expected_object = json.loads(expected) @@ -271,7 +273,7 @@ async def test_prompt_with_complex_objects(is_inline, is_streaming, template_for assert obtained_object is not None data_directory = os.path.join(os.path.dirname(__file__), "data", "prompt_with_complex_objects_expected.json") - with open(data_directory, "r") as f: + with open(data_directory) as f: expected = f.read() expected_object = json.loads(expected) @@ -341,7 +343,7 @@ async def test_prompt_with_helper_functions(is_inline, is_streaming, template_fo assert obtained_object is not None data_directory = os.path.join(os.path.dirname(__file__), "data", "prompt_with_helper_functions_expected.json") - with open(data_directory, "r") as f: + with open(data_directory) as f: expected = f.read() expected_object = json.loads(expected) @@ -400,7 +402,7 @@ async def test_prompt_with_simple_variable(is_inline, is_streaming, template_for assert obtained_object is not None data_directory = os.path.join(os.path.dirname(__file__), "data", "prompt_with_simple_variable_expected.json") - with open(data_directory, "r") as f: + with open(data_directory) as f: expected = f.read() expected_object = json.loads(expected) @@ -458,7 +460,7 @@ async def test_simple_prompt(is_inline, is_streaming, template_format, prompt): assert obtained_object is not None data_directory = os.path.join(os.path.dirname(__file__), "data", "prompt_simple_expected.json") - with open(data_directory, "r") as f: + with open(data_directory) as f: expected = f.read() expected_object = json.loads(expected) @@ -500,7 +502,7 @@ async def test_yaml_prompt(is_streaming, prompt_path, expected_result_path, kern kernel.add_service(ai_service) prompt_dir = os.path.join(os.path.dirname(__file__), "data", f"{prompt_path}") - with open(prompt_dir, "r") as f: + with open(prompt_dir) as f: prompt_str = f.read() function = KernelFunctionFromPrompt.from_yaml(yaml_str=prompt_str, plugin_name="yaml_plugin") @@ -513,7 +515,7 @@ async def test_yaml_prompt(is_streaming, prompt_path, expected_result_path, kern assert obtained_object is not None data_directory = os.path.join(os.path.dirname(__file__), "data", f"{expected_result_path}") - with open(data_directory, "r") as f: + with open(data_directory) as f: expected = f.read() expected_object = json.loads(expected) @@ -583,7 +585,6 @@ async def mock_request(request: httpx.Request): @pytest.mark.asyncio async def test_openapi_get_lights(kernel: Kernel): - request_content = await setup_openapi_function_call( kernel, function_name="GetLights", arguments=KernelArguments(roomId=1) ) @@ -597,7 +598,6 @@ async def test_openapi_get_lights(kernel: Kernel): @pytest.mark.asyncio async def test_openapi_get_light_by_id(kernel: Kernel): - request_content = await setup_openapi_function_call( kernel, function_name="GetLightById", arguments=KernelArguments(id=1) ) @@ -610,7 +610,6 @@ async def test_openapi_get_light_by_id(kernel: Kernel): @pytest.mark.asyncio async def test_openapi_delete_light_by_id(kernel: Kernel): - request_content = await setup_openapi_function_call( kernel, function_name="DeleteLightById", arguments=KernelArguments(id=1) ) @@ -623,7 +622,6 @@ async def test_openapi_delete_light_by_id(kernel: Kernel): @pytest.mark.asyncio async def test_openapi_create_lights(kernel: Kernel): - request_content = await setup_openapi_function_call( kernel, function_name="CreateLights", arguments=KernelArguments(roomId=1, lightName="disco") ) @@ -636,7 +634,6 @@ async def test_openapi_create_lights(kernel: Kernel): @pytest.mark.asyncio async def test_openapi_put_light_by_id(kernel: Kernel): - request_content = await setup_openapi_function_call( kernel, function_name="PutLightById", arguments=KernelArguments(id=1, hexColor="11EE11") ) diff --git a/python/tests/unit/functions/test_kernel_function_decorators.py b/python/tests/unit/functions/test_kernel_function_decorators.py index e5b52dd15e29..3d65429524e9 100644 --- a/python/tests/unit/functions/test_kernel_function_decorators.py +++ b/python/tests/unit/functions/test_kernel_function_decorators.py @@ -34,7 +34,7 @@ def func_with_name(self, input): @kernel_function def func_docstring_as_description(self, input): - """description""" + """Description.""" return input @kernel_function @@ -117,7 +117,7 @@ def test_kernel_function_with_name_specified(): def test_kernel_function_docstring_as_description(): decorator_test = MiscClass() my_func = getattr(decorator_test, "func_docstring_as_description") - assert my_func.__kernel_function_description__ == "description" + assert my_func.__kernel_function_description__ == "Description." def test_kernel_function_param_annotated(): diff --git a/python/tests/unit/services/test_service_utils.py b/python/tests/unit/services/test_service_utils.py index 262d22ca4eb2..1948b60444a3 100644 --- a/python/tests/unit/services/test_service_utils.py +++ b/python/tests/unit/services/test_service_utils.py @@ -24,7 +24,7 @@ class StringPlugin: def get_weather( self, location: Annotated[str, "The location to get the weather for."] ) -> Annotated[str, "The weather for the location."]: - return "The weather in {} is sunny.".format(location) + return f"The weather in {location} is sunny." class ComplexRequest(KernelBaseModel): diff --git a/python/tests/unit/text/test_text_chunker.py b/python/tests/unit/text/test_text_chunker.py index f7c577d40709..34054e5d1abc 100644 --- a/python/tests/unit/text/test_text_chunker.py +++ b/python/tests/unit/text/test_text_chunker.py @@ -13,7 +13,6 @@ def test_split_empty_string(): """Test split_plain_text_lines() with empty string""" - text = "" max_token_per_line = 10 @@ -25,7 +24,6 @@ def test_split_empty_string(): def test_split_plain_text_lines_with_token_count(): """Test split_plain_text_lines() with external token counter""" - text = "This is a test of the emergency broadcast system. This is only a test." max_token_per_line = 8 @@ -46,7 +44,6 @@ def test_split_plain_text_lines_with_token_count(): def test_split_plain_text_lines_half(): """Test split_plain_text_lines() with external token counter""" - text_1 = "This is a test of. cutting. at the half point." text_2 = "This is a test of . cutting. at the half point." @@ -63,7 +60,6 @@ def test_split_plain_text_lines_half(): def test_split_plain_text_lines(): """Test split_plain_text_lines()""" - text = "This is a test of the emergency broadcast system. This is only a test." max_token_per_line = 13 @@ -78,7 +74,6 @@ def test_split_plain_text_lines(): def test_split_markdown_paragraph(): """Test split_markdown_paragraph()""" - text = [ "This is a test of the emergency broadcast system. This is only a test.", "We repeat, this is only a test. A unit test.", @@ -98,7 +93,6 @@ def test_split_markdown_paragraph(): def test_split_text_paragraph(): """Test _split_text_paragraph()""" - text = [ "This is a test of the emergency broadcast system. This is only a test.", "We repeat, this is only a test. A unit test.", @@ -117,7 +111,6 @@ def test_split_text_paragraph(): def test_split_markdown_lines(): """Test split_markdown_lines()""" - text = "This is a test of the emergency broadcast system. This is only a test." max_token_per_line = 15 @@ -132,7 +125,6 @@ def test_split_markdown_lines(): def test_split_text_paragraph_empty_input(): """Test split_paragraph() with empty input""" - text = [] max_token_per_line = 13 @@ -143,7 +135,6 @@ def test_split_text_paragraph_empty_input(): def test_split_markdown_paragraph_empty_input(): """Test split_paragraph() with empty input""" - text = [] max_token_per_line = 10 @@ -154,7 +145,6 @@ def test_split_markdown_paragraph_empty_input(): def test_split_text_paragraph_evenly(): """Test split_paragraph() with evenly split input""" - text = [ "This is a test of the emergency broadcast system. This is only a test.", "We repeat, this is only a test. A unit test.", @@ -177,7 +167,6 @@ def test_split_text_paragraph_evenly(): def test_split_text_paragraph_evenly_2(): """Test split_paragraph() with evenly split input""" - text = [ "The gentle breeze rustled the autumn leaves on the tree branches. " + "She smiled and walked away.", "The sun set over the horizon peacefully, the beautiful star. Cats love boxes.", @@ -204,9 +193,7 @@ def test_split_text_paragraph_evenly_2(): def test_split_paragraph_newline(): - """ - a plaintext example that splits on \r or \n - """ + """A plaintext example that splits on \r or \n""" text = [ "This is a test of the emergency broadcast system\r\nThis is only a test", "We repeat this is only a test\nA unit test", @@ -226,9 +213,7 @@ def test_split_paragraph_newline(): def test_split_paragraph_punctuation(): - """ - a plaintext example that splits on ? or ! - """ + """A plaintext example that splits on ? or !""" text = [ "This is a test of the emergency broadcast system. This is only a test", "We repeat, this is only a test? A unit test", @@ -249,9 +234,7 @@ def test_split_paragraph_punctuation(): def test_split_paragraph_semicolon(): - """ - a plaintext example that splits on ; - """ + """A plaintext example that splits on ;""" text = [ "This is a test of the emergency broadcast system; This is only a test", "We repeat; this is only a test; A unit test", @@ -271,9 +254,7 @@ def test_split_paragraph_semicolon(): def test_split_paragraph_colon(): - """ - a plaintext example that splits on : - """ + """A plaintext example that splits on :""" text = [ "This is a test of the emergency broadcast system: This is only a test", "We repeat: this is only a test: A unit test", @@ -293,9 +274,7 @@ def test_split_paragraph_colon(): def test_split_paragraph_commas(): - """ - a plaintext example that splits on , - """ + """A plaintext example that splits on ,""" text = [ "This is a test of the emergency broadcast system, This is only a test", "We repeat, this is only a test, A unit test", @@ -315,9 +294,7 @@ def test_split_paragraph_commas(): def test_split_paragraph_closing_brackets(): - """ - a plaintext example that splits on closing brackets - """ + """A plaintext example that splits on closing brackets""" text = [ "This is a test of the emergency broadcast system) This is only a test", "We repeat) this is only a test) A unit test", @@ -337,9 +314,7 @@ def test_split_paragraph_closing_brackets(): def test_split_paragraph_spaces(): - """ - a plaintext example that splits on spaces - """ + """A plaintext example that splits on spaces""" text = [ "This is a test of the emergency broadcast system This is only a test", "We repeat this is only a test A unit test", @@ -359,9 +334,7 @@ def test_split_paragraph_spaces(): def test_split_paragraph_hyphens(): - """ - a plaintext example that splits on hyphens - """ + """A plaintext example that splits on hyphens""" text = [ "This is a test of the emergency broadcast system-This is only a test", "We repeat-this is only a test-A unit test", @@ -381,9 +354,7 @@ def test_split_paragraph_hyphens(): def test_split_paragraph_nodelimiters(): - """ - a plaintext example that splits on spaces - """ + """A plaintext example that splits on spaces""" text = [ "Thisisatestoftheemergencybroadcastsystem", "Thisisonlyatest", @@ -404,9 +375,7 @@ def test_split_paragraph_nodelimiters(): def test_split_md_on_dot(): - """ - a markdown example that splits on . - """ + """A markdown example that splits on .""" text = [ "This is a test of the emergency broadcast\n system.This\n is only a test", "We repeat. this is only a test. A unit test", @@ -426,9 +395,7 @@ def test_split_md_on_dot(): def test_split_md_on_colon(): - """ - a markdown example that splits on : - """ + """A markdown example that splits on :""" text = [ "This is a test of the emergency broadcast system: This is only a test", "We repeat: this is only a test: A unit test", @@ -448,9 +415,7 @@ def test_split_md_on_colon(): def test_split_md_on_punctuation(): - """ - a markdown example that splits on punctuation - """ + """A markdown example that splits on punctuation""" text = [ "This is a test of the emergency broadcast\n system?This\n is only a test", "We repeat? this is only a test! A unit test", @@ -470,9 +435,7 @@ def test_split_md_on_punctuation(): def test_split_md_on_semicolon(): - """ - a markdown example that splits on semicolons - """ + """A markdown example that splits on semicolons""" text = [ "This is a test of the emergency broadcast system; This is only a test", "We repeat; this is only a test; A unit test", @@ -492,9 +455,7 @@ def test_split_md_on_semicolon(): def test_split_md_on_commas(): - """ - a markdown example that splits on commas - """ + """A markdown example that splits on commas""" test = [ "This is a test of the emergency broadcast system, This is only a test", "We repeat, this is only a test, A unit test", @@ -514,9 +475,7 @@ def test_split_md_on_commas(): def test_split_md_on_brackets(): - """ - a markdown example that splits on brackets - """ + """A markdown example that splits on brackets""" test = [ "This is a test of the emergency broadcast system) This is only a test.", "We repeat [this is only a test] A unit test", @@ -536,9 +495,7 @@ def test_split_md_on_brackets(): def test_split_md_on_spaces(): - """ - a markdown example that splits on spaces - """ + """A markdown example that splits on spaces""" test = [ "This is a test of the emergency broadcast system This is only a test", "We repeat this is only a test A unit test",