diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 7730b45551..9b83abf273 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,29 +5,57 @@
version: 2
updates:
+ # Maintain dependencies for nuget
+ - package-ecosystem: "nuget"
+ directory: "samples/apps/copilot-chat-app/webapi"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ labels:
+ - "copilot chat"
+ - "dependencies"
+
# Maintain dependencies for nuget
- package-ecosystem: "nuget"
directory: "dotnet/"
schedule:
interval: "weekly"
+ day: "monday"
+ ignore:
+ # For all System.* and Microsoft.Extensions/Bcl.* packages, ignore all major version updates
+ - dependency-name: "System.*"
+ update-types: ["version-update:semver-major"]
+ - dependency-name: "Microsoft.Extensions.*"
+ update-types: ["version-update:semver-major"]
+ - dependency-name: "Microsoft.Bcl.*"
+ update-types: ["version-update:semver-major"]
+ labels:
+ - ".NET"
+ - "dependencies"
# Maintain dependencies for nuget
- package-ecosystem: "nuget"
- directory: "samples/"
+ directory: "samples/dotnet"
schedule:
interval: "weekly"
-
+ day: "monday"
+
# Maintain dependencies for npm
- package-ecosystem: "npm"
directory: "samples/apps"
schedule:
interval: "weekly"
+ day: "monday"
# Maintain dependencies for pip
- package-ecosystem: "pip"
directory: "python/"
schedule:
interval: "weekly"
+ day: "monday"
+ labels:
+ - "python"
+ - "dependencies"
# Maintain dependencies for github-actions
- package-ecosystem: "github-actions"
@@ -36,3 +64,4 @@ updates:
directory: "/"
schedule:
interval: "weekly"
+ day: "monday"
diff --git a/.github/labeler.yml b/.github/labeler.yml
index ea26d78e57..64d48e7989 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -1,10 +1,10 @@
# Add 'kernel' label to any change within Connectors, Extensions, Skills, and tests directories
kernel:
- - "dotnet/src/Connectors/**/*"
- - "dotnet/src/Extensions/**/*"
- - "dotnet/src/Skills/**/*"
- - "dotnet/src/IntegrationTests/**/*"
- - "dotnet/src/SemanticKernel.UnitTests/**/*"
+ - dotnet/src/Connectors/**/*
+ - dotnet/src/Extensions/**/*
+ - dotnet/src/Skills/**/*
+ - dotnet/src/IntegrationTests/**/*
+ - dotnet/src/SemanticKernel.UnitTests/**/*
# Add 'kernel.core' label to any change within the 'SemanticKernel', 'SemanticKernel.Abstractions', or 'SemanticKernel.MetaPackage' directories
kernel.core:
@@ -16,11 +16,28 @@ kernel.core:
python:
- python/**/*
+# Add 'java' label to any change within the 'java' directory
+java:
+ - java/**/*
+
# Add 'samples' label to any change within the 'samples' directory
samples:
- samples/**/*
# Add '.NET' label to any change within samples or kernel 'dotnet' directories.
.NET:
- - dotnet/src/**/*
- - samples/**/dotnet/**/*
+ - dotnet/**/*
+
+# Add 'copilot chat' label to any change within the 'samples/apps/copilot-chat-app' directory
+copilot chat:
+ - samples/apps/copilot-chat-app/**/*
+
+# Add 'documentation' label to any change within the 'docs' directory, or any '.md' files
+documentation:
+ - docs/**/*
+ - '**/*.md'
+
+# Add 'memory' label to any memory connectors in dotnet/ or python/
+memory:
+ - dotnet/src/Connectors/Connectors.Memory.*/**/*
+ - python/semantic_kernel/connectors/memory/**/*
diff --git a/.github/workflows/copilot-chat-tests.yml b/.github/workflows/copilot-chat-tests.yml
index 3dbfac05a9..c530e846a3 100644
--- a/.github/workflows/copilot-chat-tests.yml
+++ b/.github/workflows/copilot-chat-tests.yml
@@ -2,9 +2,9 @@ name: Copilot Chat Tests
on:
workflow_dispatch:
push:
- branches: [ "main", "feature*" ]
- paths:
- - 'samples/apps/copilot-chat-app/**'
+ branches: ["main", "feature*"]
+ paths:
+ - "samples/apps/copilot-chat-app/**"
permissions:
contents: read
@@ -17,70 +17,81 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v3
- - uses: actions/setup-node@v3
- with:
- node-version: 16
- cache-dependency-path: samples/apps/copilot-chat-app/webapp/yarn.lock
- cache: 'yarn'
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16
+ cache-dependency-path: samples/apps/copilot-chat-app/webapp/yarn.lock
+ cache: "yarn"
- - name: Setup .NET
- uses: actions/setup-dotnet@v3
- with:
- dotnet-version: 6.0.x
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 6.0.x
- - name: Install dependencies
- run: yarn install
+ - name: Install dependencies
+ run: yarn install
- - name: Install Playwright Browsers
- run: yarn playwright install --with-deps
+ - name: Install Playwright Browsers
+ run: yarn playwright install --with-deps
- - name: Update AIService configuration
- working-directory: samples/apps/copilot-chat-app/webapi
- env:
- AzureOpenAI__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }}
- AzureOpenAI__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }}
- run: |
- dotnet dev-certs https
- dotnet user-secrets set "AIService:Key" "$AzureOpenAI__ApiKey"
- dotnet user-secrets set "AIService:Endpoint" "$AzureOpenAI__Endpoint"
+ - name: Update AIService configuration
+ working-directory: samples/apps/copilot-chat-app/webapi
+ env:
+ AzureOpenAI__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }}
+ AzureOpenAI__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }}
+ run: |
+ dotnet dev-certs https
+ dotnet user-secrets set "AIService:Key" "$AzureOpenAI__ApiKey"
+ dotnet user-secrets set "AIService:Endpoint" "$AzureOpenAI__Endpoint"
- - name: Start service in background
- working-directory: samples/apps/copilot-chat-app/webapi
- run: |
- dotnet run > service-log.txt 2>&1 &
- for attempt in {0..20}; do
- jobs
- echo 'Waiting for service to start...';
- if curl -k https://localhost:40443/healthz; then
- echo;
- echo 'Service started';
- break;
- fi;
+ - name: Start service in background
+ working-directory: samples/apps/copilot-chat-app/webapi
+ run: |
+ dotnet run > service-log.txt 2>&1 &
+ for attempt in {0..20}; do
+ jobs
+ echo 'Waiting for service to start...';
+ if curl -k https://localhost:40443/healthz; then
+ echo;
+ echo 'Service started';
+ break;
+ fi;
- sleep 5;
- done
+ sleep 5;
+ done
- - name: Run Playwright tests
- env:
- REACT_APP_BACKEND_URI: https://localhost:40443/
- REACT_APP_AAD_CLIENT_ID: ${{ secrets.COPILOT_CHAT_REACT_APP_AAD_CLIENT_ID }}
- REACT_APP_AAD_AUTHORITY: https://login.microsoftonline.com/common
- REACT_APP_TEST_USER_ACCOUNT: ${{ secrets.COPILOT_CHAT_TEST_USER_ACCOUNT }}
- REACT_APP_TEST_USER_PASSWORD: ${{ secrets.COPILOT_CHAT_TEST_USER_PASSWORD }}
- run: yarn playwright test
+ - name: Run Playwright tests
+ env:
+ REACT_APP_BACKEND_URI: https://localhost:40443/
+ REACT_APP_AAD_CLIENT_ID: ${{ secrets.COPILOT_CHAT_REACT_APP_AAD_CLIENT_ID }}
+ REACT_APP_AAD_AUTHORITY: https://login.microsoftonline.com/common
- - uses: actions/upload-artifact@v3
- if: always()
- with:
- name: playwright-report
- path: samples/apps/copilot-chat-app/webapp/playwright-report/
- retention-days: 30
+ REACT_APP_TEST_USER_ACCOUNT1: ${{ secrets.COPILOT_CHAT_TEST_USER_ACCOUNT1 }}
+ REACT_APP_TEST_USER_ACCOUNT1_INITIALS: ${{ secrets.COPILOT_CHAT_TEST_USER_ACCOUNT1_INITIALS }}
+ REACT_APP_TEST_USER_ACCOUNT2: ${{ secrets.COPILOT_CHAT_TEST_USER_ACCOUNT2 }}
+ REACT_APP_TEST_USER_PASSWORD: ${{ secrets.COPILOT_CHAT_TEST_USER_PASSWORD }}
- - uses: actions/upload-artifact@v3
- if: always()
- with:
- name: service-log
- path: samples/apps/copilot-chat-app/webapi/service-log.txt
- retention-days: 30
+ REACT_APP_TEST_JIRA_EMAIL: ${{ secrets.COPILOT_CHAT_TEST_JIRA_EMAIL }}
+ REACT_APP_TEST_JIRA_ACCESS_TOKEN: ${{ secrets.COPILOT_CHAT_TEST_JIRA_ACCESS_TOKEN }}
+ REACT_APP_TEST_JIRA_SERVER_URL: ${{ secrets.COPILOT_CHAT_TEST_JIRA_SERVER_URL }}
+
+ REACT_APP_TEST_GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ REACT_APP_TEST_GITHUB_ACCOUNT_OWNER: ${{ secrets.COPILOT_CHAT_TEST_GITHUB_ACCOUNT_OWNER }}
+ REACT_APP_TEST_GITHUB_REPOSITORY_NAME: ${{ secrets.COPILOT_CHAT_TEST_GITHUB_REPOSITORY_NAME }}
+ run: yarn playwright test
+
+ - uses: actions/upload-artifact@v3
+ if: always()
+ with:
+ name: playwright-report
+ path: samples/apps/copilot-chat-app/webapp/playwright-report/
+ retention-days: 30
+
+ - uses: actions/upload-artifact@v3
+ if: always()
+ with:
+ name: service-log
+ path: samples/apps/copilot-chat-app/webapi/service-log.txt
+ retention-days: 30
diff --git a/.github/workflows/dotnet-pr.yml b/.github/workflows/dotnet-pr.yml
index 05efaa0b30..688a01aa1e 100644
--- a/.github/workflows/dotnet-pr.yml
+++ b/.github/workflows/dotnet-pr.yml
@@ -11,8 +11,6 @@ on:
paths:
- 'dotnet/**'
- 'samples/dotnet/**'
- - '**.cs'
- - '**.csproj'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
diff --git a/.github/workflows/java-format.yml b/.github/workflows/java-format.yml
index 1507f33084..5cd460b676 100644
--- a/.github/workflows/java-format.yml
+++ b/.github/workflows/java-format.yml
@@ -19,6 +19,14 @@ jobs:
pull-requests: write
steps:
+ - name: Check for command
+ id: command
+ uses: xt0rted/slash-command-action@v2
+ continue-on-error: true
+ with:
+ command: spotless
+ reaction-type: "eyes"
+
- name: Get command
env:
BODY: ${{ github.event.comment.body }}
@@ -77,4 +85,4 @@ jobs:
"
gh pr comment $NUMBER --body "$body"
fi
- working-directory: java
\ No newline at end of file
+ working-directory: java
diff --git a/.github/workflows/label-issues.yml b/.github/workflows/label-issues.yml
new file mode 100644
index 0000000000..05545a0b01
--- /dev/null
+++ b/.github/workflows/label-issues.yml
@@ -0,0 +1,45 @@
+name: Label issues
+on:
+ issues:
+ types:
+ - reopened
+ - opened
+
+jobs:
+ label_issues:
+ name: "Issue: add labels"
+ if: ${{ github.event.action == 'opened' || github.event.action == 'reopened' }}
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ steps:
+ - uses: actions/github-script@v6
+ with:
+ github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
+ script: |
+ // Get the issue body and title
+ const body = context.payload.issue.body
+ let title = context.payload.issue.title
+
+ // Define the labels array
+ let labels = ["triage"]
+
+ // Check if the body or the title contains the word 'python' (case-insensitive)
+ if (body.match(/python/i) || title.match(/python/i)) {
+ // Add the 'python' label to the array
+ labels.push("python")
+ }
+
+ // Check if the body or the title contains the word 'java' (case-insensitive)
+ if (body.match(/java/i) || title.match(/java/i)) {
+ // Add the 'java' label to the array
+ labels.push("java")
+ }
+
+ // Add the labels to the issue
+ github.rest.issues.addLabels({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ labels: labels
+ });
diff --git a/.github/workflows/label.yml b/.github/workflows/label-pr.yml
similarity index 82%
rename from .github/workflows/label.yml
rename to .github/workflows/label-pr.yml
index 92b13d05de..61e77b7ff1 100644
--- a/.github/workflows/label.yml
+++ b/.github/workflows/label-pr.yml
@@ -5,11 +5,11 @@
# file with configuration. For more information, see:
# https://github.com/actions/labeler
-name: Labeler
+name: Label pull request
on: [pull_request_target]
jobs:
- label:
+ add_label:
runs-on: ubuntu-latest
permissions:
contents: read
@@ -18,4 +18,4 @@ jobs:
steps:
- uses: actions/labeler@v4
with:
- repo-token: "${{ secrets.GITHUB_TOKEN }}"
+ repo-token: "${{ secrets.GH_ACTIONS_PR_WRITE }}"
diff --git a/.github/workflows/label-title-prefix.yml b/.github/workflows/label-title-prefix.yml
new file mode 100644
index 0000000000..7b3983341d
--- /dev/null
+++ b/.github/workflows/label-title-prefix.yml
@@ -0,0 +1,74 @@
+name: Label title prefix
+on:
+ issues:
+ types: [ labeled ]
+ pull_request_target:
+ types: [ labeled ]
+
+jobs:
+ add_title_prefix:
+ name: "Issue/PR: add title prefix"
+ continue-on-error: true
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+
+ steps:
+ - uses: actions/github-script@v6
+ name: "Issue/PR: update title"
+ with:
+ github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
+ script: |
+ let prefixLabels = {
+ "python": "Python",
+ "java": "Java",
+ ".NET": ".Net",
+ "copilot chat": "Copilot Chat"
+ };
+
+ function addTitlePrefix(title, prefix)
+ {
+ // Update the title based on the label and prefix
+ // Check if the title starts with the prefix (case-sensitive)
+ if (!title.startsWith(prefix + ": ")) {
+ // If not, check if the first word is the label (case-insensitive)
+ if (title.match(new RegExp(`^${prefix}`, 'i'))) {
+ // If yes, replace it with the prefix (case-sensitive)
+ title = title.replace(new RegExp(`^${prefix}`, 'i'), prefix);
+ } else {
+ // If not, prepend the prefix to the title
+ title = prefix + ": " + title;
+ }
+ }
+
+ return title;
+ }
+
+ labelAdded = context.payload.label.name
+
+ // Check if the issue or PR has the label
+ if (labelAdded in prefixLabels) {
+ let prefix = prefixLabels[labelAdded];
+ switch(context.eventName) {
+ case 'issues':
+ github.rest.issues.update({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ title: addTitlePrefix(context.payload.issue.title, prefix)
+ });
+ break
+
+ case 'pull_request_target':
+ github.rest.pulls.update({
+ pull_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ title: addTitlePrefix(context.payload.pull_request.title, prefix)
+ });
+ break
+ default:
+ core.setFailed('Unrecognited eventName: ' + context.eventName);
+ }
+ }
diff --git a/.github/workflows/python-integration-tests.yml b/.github/workflows/python-integration-tests.yml
index ad77410c93..78e576d4c7 100644
--- a/.github/workflows/python-integration-tests.yml
+++ b/.github/workflows/python-integration-tests.yml
@@ -7,12 +7,11 @@ name: Python Integration Tests
on:
workflow_dispatch:
push:
- branches: [ "main"]
- paths:
- - 'python/**'
+ branches: ["main"]
+ paths:
+ - "python/**"
schedule:
- - cron: '0 */12 * * *' # Run every 12 hours: midnight UTC and noon UTC
-
+ - cron: "0 */12 * * *" # Run every 12 hours: midnight UTC and noon UTC
permissions:
contents: read
@@ -25,44 +24,45 @@ jobs:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
- os: [ ubuntu-latest, windows-latest, macos-latest ]
-
+ os: [ubuntu-latest, windows-latest, macos-latest]
+
steps:
- - uses: actions/checkout@v3
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
- with:
- python-version: ${{ matrix.python-version }}
- - name: Install dependencies wtih hnswlib native disabled
- if: matrix.os == 'macos-latest' && matrix.python-version == '3.11'
- run: |
- export HNSWLIB_NO_NATIVE=1
- python -m pip install --upgrade pip setuptools wheel
- python -m pip install poetry pytest
- cd python && poetry install --with hugging_face --with chromadb --with weaviate
- - name: Install dependencies wtih hnswlib native enabled
- if: matrix.os != 'macos-latest' || matrix.python-version != '3.11'
- run: |
- python -m pip install --upgrade pip setuptools wheel
- python -m pip install poetry pytest
- cd python && poetry install --with hugging_face --with chromadb --with weaviate
- - name: Run Integration Tests
- shell: bash
- env: # Set Azure credentials secret as an input
- HNSWLIB_NO_NATIVE: 1
- Python_Integration_Tests: Python_Integration_Tests
- AzureOpenAI__Label: azure-text-davinci-003
- AzureOpenAIEmbedding__Label: azure-text-embedding-ada-002
- AzureOpenAI__DeploymentName: ${{ vars.AZUREOPENAI__DEPLOYMENTNAME }}
- AzureOpenAIChat__DeploymentName: ${{ vars.AZUREOPENAI__CHAT__DEPLOYMENTNAME }}
- AzureOpenAIEmbeddings__DeploymentName: ${{ vars.AZUREOPENAIEMBEDDING__DEPLOYMENTNAME }}
- AzureOpenAI__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }}
- AzureOpenAIEmbeddings__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }}
- AzureOpenAI__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }}
- AzureOpenAIEmbeddings__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }}
- Bing__ApiKey: ${{ secrets.BING__APIKEY }}
- OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }}
- run: |
- cd python
- poetry run pytest ./tests/integration
-
+ - uses: actions/checkout@v3
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies with hnswlib native disabled
+ if: matrix.os == 'macos-latest' && matrix.python-version == '3.11'
+ run: |
+ export HNSWLIB_NO_NATIVE=1
+ python -m pip install --upgrade pip setuptools wheel
+ python -m pip install poetry pytest
+ cd python && poetry install --with hugging_face --with chromadb --with weaviate
+ - name: Install dependencies with hnswlib native enabled
+ if: matrix.os != 'macos-latest' || matrix.python-version != '3.11'
+ run: |
+ python -m pip install --upgrade pip setuptools wheel
+ python -m pip install poetry pytest
+ cd python && poetry install --with hugging_face --with chromadb --with weaviate
+ - name: Run Integration Tests
+ shell: bash
+ env: # Set Azure credentials secret as an input
+ HNSWLIB_NO_NATIVE: 1
+ Python_Integration_Tests: Python_Integration_Tests
+ AzureOpenAI__Label: azure-text-davinci-003
+ AzureOpenAIEmbedding__Label: azure-text-embedding-ada-002
+ AzureOpenAI__DeploymentName: ${{ vars.AZUREOPENAI__DEPLOYMENTNAME }}
+ AzureOpenAIChat__DeploymentName: ${{ vars.AZUREOPENAI__CHAT__DEPLOYMENTNAME }}
+ AzureOpenAIEmbeddings__DeploymentName: ${{ vars.AZUREOPENAIEMBEDDING__DEPLOYMENTNAME }}
+ AzureOpenAI__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }}
+ AzureOpenAIEmbeddings__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }}
+ AzureOpenAI__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }}
+ AzureOpenAIEmbeddings__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }}
+ Bing__ApiKey: ${{ secrets.BING__APIKEY }}
+ OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }}
+ Pinecone__ApiKey: ${{ secrets.PINECONE__APIKEY }}
+ Pinecone__Environment: ${{ secrets.PINECONE__ENVIRONMENT }}
+ run: |
+ cd python
+ poetry run pytest ./tests/integration
diff --git a/.gitignore b/.gitignore
index 4310460168..530b14105f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -476,4 +476,5 @@ playwright-report/
# Static Web App deployment config
swa-cli.config.json
-**/copilot-chat-app/webapp/build
\ No newline at end of file
+**/copilot-chat-app/webapp/build
+**/copilot-chat-app/webapp/node_modules
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index f07c5679f7..dae66ae37d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -89,5 +89,6 @@
],
},
"java.debug.settings.onBuildFailureProceed": true,
- "java.compile.nullAnalysis.mode": "disabled"
+ "java.compile.nullAnalysis.mode": "disabled",
+ "dotnet.defaultSolution": "dotnet\\SK-dotnet.sln"
}
\ No newline at end of file
diff --git a/FEATURE_MATRIX.md b/FEATURE_MATRIX.md
index 6d66eadf9a..451c85b3df 100644
--- a/FEATURE_MATRIX.md
+++ b/FEATURE_MATRIX.md
@@ -39,21 +39,21 @@
|-----------------------------------|:---:|:------:|:----:|-------|
| TextMemory Skill | â
| â
| đ | |
| ConversationSummary Skill | â
| â
| â | |
-| FileIO Skill | â
| â
| â | |
-| Http Skill | â
| â
| â | |
-| Math Skill | â
| â
| â | |
+| FileIO Skill | â
| â
| â
| |
+| Http Skill | â
| â
| â
| |
+| Math Skill | â
| â
| â
| |
| Text Skill | â
| â
| đ | |
-| Time Skill | â
| â
| đ | |
-| Wait Skill | â
| â | â | |
+| Time Skill | â
| â
| â
| |
+| Wait Skill | â
| â | â
| |
## Planning
| | C# | Python | Java | Notes |
|-----------------------------------|:---:|:------:|:----:|-------|
-| Plan | â
| đ | â | Plan object model to be completed |
-| BasicPlanner | â | â
| â | |
-| ActionPlanner | â
| â | â | |
-| SequentialPlanner | â
| â | â | |
+| Plan | â
| đ | â | Plan object model to be completed |
+| BasicPlanner | â | â
| â | |
+| ActionPlanner | â
| â | đ | |
+| SequentialPlanner | â
| â | đ | |
## Memory Connectors, Vector storage
diff --git a/README.md b/README.md
index 90d2ff27d1..4298994ed0 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,12 @@
[](https://pypi.org/project/semantic-kernel/)
[](https://www.nuget.org/packages/Microsoft.SemanticKernel/)
-[](https://github.com/microsoft/semantic-kernel/actions/workflows/dotnet-ci.yml)
+[](https://github.com/microsoft/semantic-kernel/actions/workflows/dotnet-ci-docker.yml)
+[](https://github.com/microsoft/semantic-kernel/actions/workflows/dotnet-ci-windows.yml)
[](https://github.com/microsoft/semantic-kernel/blob/main/LICENSE)
[](https://aka.ms/SKDiscord)
-> âšī¸ **NOTE**:This project is just like AI and will evolve quickly.
+> âšī¸ **NOTE**: This project is just like AI and will evolve quickly.
> We invite you to join us in developing the Semantic Kernel together!
> Please contribute by
> using GitHub [Discussions](https://github.com/microsoft/semantic-kernel/discussions),
@@ -43,16 +44,24 @@ in the history of computing.
Semantic Kernel is available to explore AI and build apps with C# and Python:
-
-
-
-
+
+
+
+
+
+
+ |
+
+
+
+ |
+
+
+
See the [Feature Matrix](FEATURE_MATRIX.md) to see a breakdown of feature parity between C# and Python.
@@ -60,14 +69,16 @@ The quickest way to get started with the basics is to get an API key
(OpenAI or Azure OpenAI)
and to run one of the C# or Python console applications/scripts:
-For C#:
+### For C#:
+
1. Create a new console app.
2. Add the semantic kernel nuget `Microsoft.SemanticKernel`.
3. Copy the code from [here](dotnet/README.md) into the app `Program.cs` file.
4. Replace the configuration placeholders for API key and other params with your key and settings.
5. Run with `F5` or `dotnet run`
-For Python:
+### For Python:
+
1. Install the pip package: `python -m pip install semantic-kernel`.
2. Create a new script e.g. `hello-world.py`.
3. Store your API key and settings in an `.env` file as described [here](python/README.md).
diff --git a/compliance.yml b/compliance.yml
deleted file mode 100644
index 70cc8a253f..0000000000
--- a/compliance.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-trigger:
-- main
-
-# no PR triggers
-pr: none
-
-pool:
- vmImage: 'windows-latest'
-
-steps:
-- task: CredScan@3
-
-- task: ESLint@1
- inputs:
- Configuration: 'recommended'
- TargetType: 'eslint'
- ErrorLevel: 'warn'
-
-- task: UseDotNet@2
- displayName: 'Use .NET 7.0'
- inputs:
- packageType: 'sdk'
- version: '7.x'
-
-- task: Semmle@1
- env:
- SYSTEM_ACCESSTOKEN: $(System.AccessToken)
- inputs:
- sourceCodeDirectory: '$(Build.SourcesDirectory)'
- language: 'csharp'
- buildCommandsString: '$(Agent.ToolsDirectory)\dotnet\dotnet.exe restore $(BUILD.SourcesDirectory)\dotnet\SK-dotnet.sln#$(Agent.ToolsDirectory)\dotnet\dotnet.exe build $(BUILD.SourcesDirectory)\dotnet\SK-dotnet.sln'
- querySuite: 'Recommended'
- timeout: '1800'
- ram: '16384'
- addProjectDirToScanningExclusionList: true
-
-# Usage of System.AccessToken is only required for uploading
-# results to CodeQL servers via variable LGTM.UploadSnapshot = true
-# If you want to analyze errors on your own or if LGTM.UploadSnapshot = false,
-# then passing this environment variable is not required.
-
-#######################################################
-# Highly Discouraged, only for backward compatibility #
-#######################################################
-# When code is hosted on GitHub and build pipeline is created on ADO then a PAT token can be used in place of AccessToken as part of environment variable
-# SYSTEM_ACCESSTOKEN: $(PATToken)
-# Where PATToken is name of the variable and value contains the actual PAT token generated by user
-
-- task: ComponentGovernanceComponentDetection@0
- inputs:
- scanType: 'Register'
- verbosity: 'Verbose'
- alertWarningLevel: 'High'
- failOnAlert: true
-
-- task: PublishSecurityAnalysisLogs@3
- inputs:
- ArtifactName: 'CodeAnalysisLogs'
- ArtifactType: 'Container'
- AllTools: true
- ToolLogsNotFoundAction: 'Standard'
diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 5123c75313..5ed9a72db9 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -9,34 +9,34 @@
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
@@ -44,7 +44,7 @@
-
+
diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln
index c2e04a6b16..48df300b8a 100644
--- a/dotnet/SK-dotnet.sln
+++ b/dotnet/SK-dotnet.sln
@@ -13,8 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{FA37
..\samples\dotnet\Directory.Packages.props = ..\samples\dotnet\Directory.Packages.props
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LoadPromptsFromCloud", "..\samples\dotnet\kernel-extension-load-prompts-from-cloud\LoadPromptsFromCloud.csproj", "{A05BF65E-085E-476C-B88A-9DA93F005416}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KernelSyntaxExamples", "samples\KernelSyntaxExamples\KernelSyntaxExamples.csproj", "{47C6F821-5103-431F-B3B8-A2868A68BB78}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MsGraphSkillsExample", "..\samples\dotnet\graph-api-skills\MsGraphSkillsExample.csproj", "{3EB61E99-C39B-4620-9482-F8DA18E48525}"
@@ -52,18 +50,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{F4243136
nuget\NUGET.md = nuget\NUGET.md
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitHubSkills", "..\samples\dotnet\github-skills\GitHubSkills.csproj", "{39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Skills.OpenAPI", "src\Skills\Skills.OpenAPI\Skills.OpenAPI.csproj", "{F2A1F81E-700E-4C0E-B021-B9EF29AA20BD}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileCompression", "..\samples\dotnet\FileCompression\FileCompression.csproj", "{BC70A5D8-2125-4C37-8C0E-C903EAFA9772}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "connectors", "connectors", "{0247C2C9-86C3-45BA-8873-28B0948EDC0C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.UnitTests", "src\Connectors\Connectors.UnitTests\Connectors.UnitTests.csproj", "{EB3FC57F-E591-4C88-BCD5-B6A1BC635168}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CopilotChatWebApi", "..\samples\apps\copilot-chat-app\webapi\CopilotChatWebApi.csproj", "{CCABF515-2C79-453E-A5A2-69C69B8D172E}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.Memory.Qdrant", "src\Connectors\Connectors.Memory.Qdrant\Connectors.Memory.Qdrant.csproj", "{5DEBAA62-F117-496A-8778-FED3604B70E2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.Memory.Sqlite", "src\Connectors\Connectors.Memory.Sqlite\Connectors.Memory.Sqlite.csproj", "{EC004F12-2F60-4EDD-B3CD-3A504900D929}"
@@ -82,8 +74,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemanticKernel.Abstractions
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemanticKernel.MetaPackage", "src\SemanticKernel.MetaPackage\SemanticKernel.MetaPackage.csproj", "{E3299033-EB81-4C4C-BCD9-E8DC40937969}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImportDocument", "..\samples\apps\copilot-chat-app\importdocument\ImportDocument.csproj", "{94E14913-D600-4448-A5C5-A268C23B2C3B}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Planning.ActionPlanner", "src\Extensions\Planning.ActionPlanner\Planning.ActionPlanner.csproj", "{994BEF0B-E277-4D10-BB13-FE670D26620D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "extensions", "extensions", "{078F96B4-09E1-4E0E-B214-F71A4F4BF633}"
@@ -165,11 +155,6 @@ Global
{A284C7EB-2248-4A75-B112-F5DCDE65410D}.Publish|Any CPU.Build.0 = Publish|Any CPU
{A284C7EB-2248-4A75-B112-F5DCDE65410D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A284C7EB-2248-4A75-B112-F5DCDE65410D}.Release|Any CPU.Build.0 = Release|Any CPU
- {A05BF65E-085E-476C-B88A-9DA93F005416}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A05BF65E-085E-476C-B88A-9DA93F005416}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A05BF65E-085E-476C-B88A-9DA93F005416}.Publish|Any CPU.ActiveCfg = Release|Any CPU
- {A05BF65E-085E-476C-B88A-9DA93F005416}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A05BF65E-085E-476C-B88A-9DA93F005416}.Release|Any CPU.Build.0 = Release|Any CPU
{47C6F821-5103-431F-B3B8-A2868A68BB78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47C6F821-5103-431F-B3B8-A2868A68BB78}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47C6F821-5103-431F-B3B8-A2868A68BB78}.Publish|Any CPU.ActiveCfg = Release|Any CPU
@@ -221,31 +206,18 @@ Global
{107156B4-5A8B-45C7-97A2-4544D7FA19DE}.Publish|Any CPU.Build.0 = Release|Any CPU
{107156B4-5A8B-45C7-97A2-4544D7FA19DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{107156B4-5A8B-45C7-97A2-4544D7FA19DE}.Release|Any CPU.Build.0 = Release|Any CPU
- {39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF}.Publish|Any CPU.ActiveCfg = Release|Any CPU
- {39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF}.Release|Any CPU.Build.0 = Release|Any CPU
{F2A1F81E-700E-4C0E-B021-B9EF29AA20BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2A1F81E-700E-4C0E-B021-B9EF29AA20BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2A1F81E-700E-4C0E-B021-B9EF29AA20BD}.Publish|Any CPU.ActiveCfg = Publish|Any CPU
{F2A1F81E-700E-4C0E-B021-B9EF29AA20BD}.Publish|Any CPU.Build.0 = Publish|Any CPU
{F2A1F81E-700E-4C0E-B021-B9EF29AA20BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2A1F81E-700E-4C0E-B021-B9EF29AA20BD}.Release|Any CPU.Build.0 = Release|Any CPU
- {BC70A5D8-2125-4C37-8C0E-C903EAFA9772}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BC70A5D8-2125-4C37-8C0E-C903EAFA9772}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BC70A5D8-2125-4C37-8C0E-C903EAFA9772}.Publish|Any CPU.ActiveCfg = Release|Any CPU
- {BC70A5D8-2125-4C37-8C0E-C903EAFA9772}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BC70A5D8-2125-4C37-8C0E-C903EAFA9772}.Release|Any CPU.Build.0 = Release|Any CPU
{EB3FC57F-E591-4C88-BCD5-B6A1BC635168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB3FC57F-E591-4C88-BCD5-B6A1BC635168}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB3FC57F-E591-4C88-BCD5-B6A1BC635168}.Publish|Any CPU.ActiveCfg = Release|Any CPU
{EB3FC57F-E591-4C88-BCD5-B6A1BC635168}.Publish|Any CPU.Build.0 = Release|Any CPU
{EB3FC57F-E591-4C88-BCD5-B6A1BC635168}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB3FC57F-E591-4C88-BCD5-B6A1BC635168}.Release|Any CPU.Build.0 = Release|Any CPU
- {CCABF515-2C79-453E-A5A2-69C69B8D172E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CCABF515-2C79-453E-A5A2-69C69B8D172E}.Publish|Any CPU.ActiveCfg = Release|Any CPU
- {CCABF515-2C79-453E-A5A2-69C69B8D172E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5DEBAA62-F117-496A-8778-FED3604B70E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5DEBAA62-F117-496A-8778-FED3604B70E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5DEBAA62-F117-496A-8778-FED3604B70E2}.Publish|Any CPU.ActiveCfg = Publish|Any CPU
@@ -299,11 +271,6 @@ Global
{E3299033-EB81-4C4C-BCD9-E8DC40937969}.Publish|Any CPU.Build.0 = Publish|Any CPU
{E3299033-EB81-4C4C-BCD9-E8DC40937969}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3299033-EB81-4C4C-BCD9-E8DC40937969}.Release|Any CPU.Build.0 = Release|Any CPU
- {94E14913-D600-4448-A5C5-A268C23B2C3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {94E14913-D600-4448-A5C5-A268C23B2C3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {94E14913-D600-4448-A5C5-A268C23B2C3B}.Publish|Any CPU.ActiveCfg = Release|Any CPU
- {94E14913-D600-4448-A5C5-A268C23B2C3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {94E14913-D600-4448-A5C5-A268C23B2C3B}.Release|Any CPU.Build.0 = Release|Any CPU
{994BEF0B-E277-4D10-BB13-FE670D26620D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{994BEF0B-E277-4D10-BB13-FE670D26620D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{994BEF0B-E277-4D10-BB13-FE670D26620D}.Publish|Any CPU.ActiveCfg = Publish|Any CPU
@@ -386,7 +353,6 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A284C7EB-2248-4A75-B112-F5DCDE65410D} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0}
- {A05BF65E-085E-476C-B88A-9DA93F005416} = {FA3720F1-C99A-49B2-9577-A940257098BF}
{47C6F821-5103-431F-B3B8-A2868A68BB78} = {FA3720F1-C99A-49B2-9577-A940257098BF}
{3EB61E99-C39B-4620-9482-F8DA18E48525} = {FA3720F1-C99A-49B2-9577-A940257098BF}
{34A7F1EF-D243-4160-A413-D713FEABCD94} = {FA3720F1-C99A-49B2-9577-A940257098BF}
@@ -398,12 +364,9 @@ Global
{9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0}
{107156B4-5A8B-45C7-97A2-4544D7FA19DE} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974}
{F4243136-252A-4459-A7C4-EE8C056D6B0B} = {158A4E5E-AEE0-4D60-83C7-8E089B2D881D}
- {39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF} = {FA3720F1-C99A-49B2-9577-A940257098BF}
{F2A1F81E-700E-4C0E-B021-B9EF29AA20BD} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974}
- {BC70A5D8-2125-4C37-8C0E-C903EAFA9772} = {FA3720F1-C99A-49B2-9577-A940257098BF}
{0247C2C9-86C3-45BA-8873-28B0948EDC0C} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0}
{EB3FC57F-E591-4C88-BCD5-B6A1BC635168} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C}
- {CCABF515-2C79-453E-A5A2-69C69B8D172E} = {FA3720F1-C99A-49B2-9577-A940257098BF}
{5DEBAA62-F117-496A-8778-FED3604B70E2} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C}
{EC004F12-2F60-4EDD-B3CD-3A504900D929} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C}
{EA61C289-7928-4B78-A9C1-7AAD61F907CD} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C}
@@ -413,7 +376,6 @@ Global
{AFA81EB7-F869-467D-8A90-744305D80AAC} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C}
{627742DB-1E52-468A-99BD-6FF1A542D25B} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0}
{E3299033-EB81-4C4C-BCD9-E8DC40937969} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0}
- {94E14913-D600-4448-A5C5-A268C23B2C3B} = {FA3720F1-C99A-49B2-9577-A940257098BF}
{994BEF0B-E277-4D10-BB13-FE670D26620D} = {078F96B4-09E1-4E0E-B214-F71A4F4BF633}
{078F96B4-09E1-4E0E-B214-F71A4F4BF633} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0}
{F51017A9-15C8-472D-893C-080046D710A6} = {078F96B4-09E1-4E0E-B214-F71A4F4BF633}
diff --git a/build.cmd b/dotnet/build.cmd
similarity index 100%
rename from build.cmd
rename to dotnet/build.cmd
diff --git a/build.sh b/dotnet/build.sh
old mode 100755
new mode 100644
similarity index 100%
rename from build.sh
rename to dotnet/build.sh
diff --git a/nuget.config b/dotnet/nuget.config
similarity index 100%
rename from nuget.config
rename to dotnet/nuget.config
diff --git a/dotnet/samples/KernelSyntaxExamples/Example39_Postgres.cs b/dotnet/samples/KernelSyntaxExamples/Example39_Postgres.cs
index af9a2b7ecf..b9ac79e1e2 100644
--- a/dotnet/samples/KernelSyntaxExamples/Example39_Postgres.cs
+++ b/dotnet/samples/KernelSyntaxExamples/Example39_Postgres.cs
@@ -20,7 +20,7 @@ public static class Example39_Postgres
dataSourceBuilder.UseVector();
using NpgsqlDataSource dataSource = dataSourceBuilder.Build();
- PostgresMemoryStore memoryStore = new(dataSource, vectorSize: 1536, schema: "public", numberOfLists: 100);
+ PostgresMemoryStore memoryStore = new(dataSource, vectorSize: 1536, schema: "public");
IKernel kernel = Kernel.Builder
.WithLogger(ConsoleLogger.Log)
diff --git a/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj b/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj
index 996ea3b9b9..0660b619b1 100644
--- a/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj
+++ b/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj
@@ -44,6 +44,10 @@
+
+
+
+
diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs
index a46f95852f..8fb4870c79 100644
--- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs
+++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs
@@ -347,9 +347,9 @@ public abstract class ClientBase
return validRole;
}
- private static void ValidateMaxTokens(int maxTokens)
+ private static void ValidateMaxTokens(int? maxTokens)
{
- if (maxTokens < 1)
+ if (maxTokens.HasValue && maxTokens < 1)
{
throw new AIException(
AIException.ErrorCodes.InvalidRequest,
diff --git a/dotnet/src/Connectors/Connectors.Memory.Pinecone/Model/IndexDefinition.cs b/dotnet/src/Connectors/Connectors.Memory.Pinecone/Model/IndexDefinition.cs
index fd1716acd4..37efe6a397 100644
--- a/dotnet/src/Connectors/Connectors.Memory.Pinecone/Model/IndexDefinition.cs
+++ b/dotnet/src/Connectors/Connectors.Memory.Pinecone/Model/IndexDefinition.cs
@@ -3,7 +3,6 @@
using System.Net.Http;
using System.Text;
using System.Text.Json.Serialization;
-using Microsoft.SemanticKernel.Connectors.Memory.Pinecone.Http;
namespace Microsoft.SemanticKernel.Connectors.Memory.Pinecone.Model;
diff --git a/dotnet/src/Connectors/Connectors.Memory.Postgres/Connectors.Memory.Postgres.csproj b/dotnet/src/Connectors/Connectors.Memory.Postgres/Connectors.Memory.Postgres.csproj
index 66f12ab2a8..b197259942 100644
--- a/dotnet/src/Connectors/Connectors.Memory.Postgres/Connectors.Memory.Postgres.csproj
+++ b/dotnet/src/Connectors/Connectors.Memory.Postgres/Connectors.Memory.Postgres.csproj
@@ -20,6 +20,9 @@
+
+
+
diff --git a/dotnet/src/Connectors/Connectors.Memory.Postgres/IPostgresDbClient.cs b/dotnet/src/Connectors/Connectors.Memory.Postgres/IPostgresDbClient.cs
index 59d69fbb0d..51064a4e38 100644
--- a/dotnet/src/Connectors/Connectors.Memory.Postgres/IPostgresDbClient.cs
+++ b/dotnet/src/Connectors/Connectors.Memory.Postgres/IPostgresDbClient.cs
@@ -1,5 +1,6 @@
īģŋ// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -13,76 +14,95 @@ namespace Microsoft.SemanticKernel.Connectors.Memory.Postgres;
public interface IPostgresDbClient
{
///
- /// Check if a collection exists.
+ /// Check if a table exists.
///
- /// The name assigned to a collection of entries.
+ /// The name assigned to a table of entries.
/// The to monitor for cancellation requests. The default is .
///
- Task DoesCollectionExistsAsync(string collectionName, CancellationToken cancellationToken = default);
+ Task DoesTableExistsAsync(string tableName, CancellationToken cancellationToken = default);
///
- /// Create a collection.
+ /// Create a table.
///
- /// The name assigned to a collection of entries.
+ /// The name assigned to a table of entries.
/// The to monitor for cancellation requests. The default is .
///
- Task CreateCollectionAsync(string collectionName, CancellationToken cancellationToken = default);
+ Task CreateTableAsync(string tableName, CancellationToken cancellationToken = default);
///
- /// Get all collections.
+ /// Get all tables.
///
/// The to monitor for cancellation requests. The default is .
- ///
- IAsyncEnumerable GetCollectionsAsync(CancellationToken cancellationToken = default);
+ /// A group of tables.
+ IAsyncEnumerable GetTablesAsync(CancellationToken cancellationToken = default);
///
- /// Delete a collection.
+ /// Delete a table.
///
- /// The name assigned to a collection of entries.
+ /// The name assigned to a table of entries.
/// The to monitor for cancellation requests. The default is .
///
- Task DeleteCollectionAsync(string collectionName, CancellationToken cancellationToken = default);
+ Task DeleteTableAsync(string tableName, CancellationToken cancellationToken = default);
///
- /// Upsert entry into a collection.
+ /// Upsert entry into a table.
///
- /// The name assigned to a collection of entries.
+ /// The name assigned to a table of entries.
/// The key of the entry to upsert.
/// The metadata of the entry.
/// The embedding of the entry.
- /// The timestamp of the entry
+ /// The timestamp of the entry. Its 'DateTimeKind' must be
/// The to monitor for cancellation requests. The default is .
///
- Task UpsertAsync(string collectionName, string key, string? metadata, Vector? embedding, long? timestamp, CancellationToken cancellationToken = default);
+ Task UpsertAsync(string tableName, string key, string? metadata, Vector? embedding, DateTime? timestamp, CancellationToken cancellationToken = default);
///
/// Gets the nearest matches to the .
///
- /// The name assigned to a collection of entries.
- /// The to compare the collection's embeddings with.
+ /// The name assigned to a table of entries.
+ /// The to compare the table's embeddings with.
/// The maximum number of similarity results to return.
/// The minimum relevance threshold for returned results.
/// If true, the embeddings will be returned in the entries.
/// The to monitor for cancellation requests. The default is .
- ///
- IAsyncEnumerable<(PostgresMemoryEntry, double)> GetNearestMatchesAsync(string collectionName, Vector embeddingFilter, int limit, double minRelevanceScore = 0, bool withEmbeddings = false, CancellationToken cancellationToken = default);
+ /// An asynchronous stream of objects that the nearest matches to the .
+ IAsyncEnumerable<(PostgresMemoryEntry, double)> GetNearestMatchesAsync(string tableName, Vector embedding, int limit, double minRelevanceScore = 0, bool withEmbeddings = false, CancellationToken cancellationToken = default);
///
/// Read a entry by its key.
///
- /// The name assigned to a collection of entries.
+ /// The name assigned to a table of entries.
/// The key of the entry to read.
- /// If true, the embeddings will be returned in the entries.
+ /// If true, the embeddings will be returned in the entry.
/// The to monitor for cancellation requests. The default is .
///
- Task ReadAsync(string collectionName, string key, bool withEmbeddings = false, CancellationToken cancellationToken = default);
+ Task ReadAsync(string tableName, string key, bool withEmbeddings = false, CancellationToken cancellationToken = default);
+
+ ///
+ /// Read multiple entries by their keys.
+ ///
+ /// The name assigned to a table of entries.
+ /// The keys of the entries to read.
+ /// If true, the embeddings will be returned in the entries.
+ /// The to monitor for cancellation requests. The default is .
+ /// An asynchronous stream of objects that match the given keys.
+ IAsyncEnumerable ReadBatchAsync(string tableName, IEnumerable keys, bool withEmbeddings = false, CancellationToken cancellationToken = default);
///
/// Delete a entry by its key.
///
- /// The name assigned to a collection of entries.
+ /// The name assigned to a table of entries.
/// The key of the entry to delete.
/// The to monitor for cancellation requests. The default is .
///
- Task DeleteAsync(string collectionName, string key, CancellationToken cancellationToken = default);
+ Task DeleteAsync(string tableName, string key, CancellationToken cancellationToken = default);
+
+ ///
+ /// Delete multiple entries by their key.
+ ///
+ /// The name assigned to a table of entries.
+ /// The keys of the entries to delete.
+ /// The to monitor for cancellation requests. The default is .
+ ///
+ Task DeleteBatchAsync(string tableName, IEnumerable keys, CancellationToken cancellationToken = default);
}
diff --git a/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresDbClient.cs b/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresDbClient.cs
index e957f01f76..0ce3756184 100644
--- a/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresDbClient.cs
+++ b/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresDbClient.cs
@@ -2,10 +2,12 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Npgsql;
+using NpgsqlTypes;
using Pgvector;
namespace Microsoft.SemanticKernel.Connectors.Memory.Postgres;
@@ -22,220 +24,249 @@ public class PostgresDbClient : IPostgresDbClient
/// Postgres data source.
/// Schema of collection tables.
/// Embedding vector size.
- /// Specifies the number of lists for indexing. Higher values can improve recall but may impact performance. The default value is 1000. More info
- public PostgresDbClient(NpgsqlDataSource dataSource, string schema, int vectorSize, int numberOfLists)
+ public PostgresDbClient(NpgsqlDataSource dataSource, string schema, int vectorSize)
{
this._dataSource = dataSource;
this._schema = schema;
this._vectorSize = vectorSize;
- this._numberOfLists = numberOfLists;
}
- ///
- /// Check if a collection exists.
- ///
- /// The name assigned to a collection of entries.
- /// The to monitor for cancellation requests. The default is .
- ///
- public async Task DoesCollectionExistsAsync(
- string collectionName,
- CancellationToken cancellationToken = default)
+ ///
+ public async Task DoesTableExistsAsync(string tableName, CancellationToken cancellationToken = default)
{
- using NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
+ NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
- using NpgsqlCommand cmd = connection.CreateCommand();
- cmd.CommandText = $@"
- SELECT table_name
- FROM information_schema.tables
- WHERE table_schema = @schema
- AND table_type = 'BASE TABLE'
- AND table_name = '{collectionName}'";
- cmd.Parameters.AddWithValue("@schema", this._schema);
-
- using var dataReader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
- if (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false))
+ await using (connection)
{
- return dataReader.GetString(dataReader.GetOrdinal("table_name")) == collectionName;
- }
+ using NpgsqlCommand cmd = connection.CreateCommand();
+ cmd.CommandText = $@"
+ SELECT table_name
+ FROM information_schema.tables
+ WHERE table_schema = @schema
+ AND table_type = 'BASE TABLE'
+ AND table_name = '{tableName}'";
+ cmd.Parameters.AddWithValue("@schema", this._schema);
- return false;
- }
+ using NpgsqlDataReader dataReader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
+ if (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false))
+ {
+ return dataReader.GetString(dataReader.GetOrdinal("table_name")) == tableName;
+ }
- ///
- /// Create a collection.
- ///
- /// The name assigned to a collection of entries.
- /// The to monitor for cancellation requests. The default is .
- ///
- public async Task CreateCollectionAsync(string collectionName, CancellationToken cancellationToken = default)
- {
- using NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
-
- await this.CreateTableAsync(connection, collectionName, cancellationToken).ConfigureAwait(false);
-
- await this.CreateIndexAsync(connection, collectionName, cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Get all collections.
- ///
- /// The to monitor for cancellation requests. The default is .
- ///
- public async IAsyncEnumerable GetCollectionsAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- using NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
-
- using NpgsqlCommand cmd = connection.CreateCommand();
- cmd.CommandText = @"
- SELECT table_name
- FROM information_schema.tables
- WHERE table_schema = @schema
- AND table_type = 'BASE TABLE'";
- cmd.Parameters.AddWithValue("@schema", this._schema);
-
- using var dataReader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
- while (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false))
- {
- yield return dataReader.GetString(dataReader.GetOrdinal("table_name"));
+ return false;
}
}
- ///
- /// Delete a collection.
- ///
- /// The name assigned to a collection of entries.
- /// The to monitor for cancellation requests. The default is .
- ///
- public async Task DeleteCollectionAsync(string collectionName, CancellationToken cancellationToken = default)
+ ///
+ public async Task CreateTableAsync(string tableName, CancellationToken cancellationToken = default)
{
- using NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
+ NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
- using NpgsqlCommand cmd = connection.CreateCommand();
- cmd.CommandText = $"DROP TABLE IF EXISTS {this.GetTableName(collectionName)}";
-
- await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
+ await using (connection)
+ {
+ using NpgsqlCommand cmd = connection.CreateCommand();
+ cmd.CommandText = $@"
+ CREATE TABLE IF NOT EXISTS {this.GetFullTableName(tableName)} (
+ key TEXT NOT NULL,
+ metadata JSONB,
+ embedding vector({this._vectorSize}),
+ timestamp TIMESTAMP WITH TIME ZONE,
+ PRIMARY KEY (key))";
+ await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
+ }
}
- ///
- /// Upsert entry into a collection.
- ///
- /// The name assigned to a collection of entries.
- /// The key of the entry to upsert.
- /// The metadata of the entry.
- /// The embedding of the entry.
- /// The timestamp of the entry
- /// The to monitor for cancellation requests. The default is .
- ///
- public async Task UpsertAsync(string collectionName, string key,
- string? metadata, Vector? embedding, long? timestamp, CancellationToken cancellationToken = default)
+ ///
+ public async IAsyncEnumerable GetTablesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
- using NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
+ NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
- using NpgsqlCommand cmd = connection.CreateCommand();
- cmd.CommandText = $@"
- INSERT INTO {this.GetTableName(collectionName)} (key, metadata, embedding, timestamp)
- VALUES(@key, @metadata, @embedding, @timestamp)
- ON CONFLICT (key)
- DO UPDATE SET metadata=@metadata, embedding=@embedding, timestamp=@timestamp";
- cmd.Parameters.AddWithValue("@key", key);
- cmd.Parameters.AddWithValue("@metadata", NpgsqlTypes.NpgsqlDbType.Jsonb, metadata ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("@embedding", embedding ?? (object)DBNull.Value);
- cmd.Parameters.AddWithValue("@timestamp", timestamp ?? (object)DBNull.Value);
+ await using (connection)
+ {
+ using NpgsqlCommand cmd = connection.CreateCommand();
+ cmd.CommandText = @"
+ SELECT table_name
+ FROM information_schema.tables
+ WHERE table_schema = @schema
+ AND table_type = 'BASE TABLE'";
+ cmd.Parameters.AddWithValue("@schema", this._schema);
- await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
+ using NpgsqlDataReader dataReader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
+ while (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false))
+ {
+ yield return dataReader.GetString(dataReader.GetOrdinal("table_name"));
+ }
+ }
}
- ///
- /// Gets the nearest matches to the .
- ///
- /// The name assigned to a collection of entries.
- /// The to compare the collection's embeddings with.
- /// The maximum number of similarity results to return.
- /// The minimum relevance threshold for returned results.
- /// If true, the embeddings will be returned in the entries.
- /// The to monitor for cancellation requests. The default is .
- ///
+ ///
+ public async Task DeleteTableAsync(string tableName, CancellationToken cancellationToken = default)
+ {
+ NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ await using (connection)
+ {
+ using NpgsqlCommand cmd = connection.CreateCommand();
+ cmd.CommandText = $"DROP TABLE IF EXISTS {this.GetFullTableName(tableName)}";
+
+ await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ public async Task UpsertAsync(string tableName, string key,
+ string? metadata, Vector? embedding, DateTime? timestamp, CancellationToken cancellationToken = default)
+ {
+ NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ await using (connection)
+ {
+ using NpgsqlCommand cmd = connection.CreateCommand();
+ cmd.CommandText = $@"
+ INSERT INTO {this.GetFullTableName(tableName)} (key, metadata, embedding, timestamp)
+ VALUES(@key, @metadata, @embedding, @timestamp)
+ ON CONFLICT (key)
+ DO UPDATE SET metadata=@metadata, embedding=@embedding, timestamp=@timestamp";
+ cmd.Parameters.AddWithValue("@key", key);
+ cmd.Parameters.AddWithValue("@metadata", NpgsqlTypes.NpgsqlDbType.Jsonb, metadata ?? (object)DBNull.Value);
+ cmd.Parameters.AddWithValue("@embedding", embedding ?? (object)DBNull.Value);
+ cmd.Parameters.AddWithValue("@timestamp", NpgsqlTypes.NpgsqlDbType.TimestampTz, timestamp ?? (object)DBNull.Value);
+
+ await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
public async IAsyncEnumerable<(PostgresMemoryEntry, double)> GetNearestMatchesAsync(
- string collectionName, Vector embeddingFilter, int limit, double minRelevanceScore = 0, bool withEmbeddings = false,
+ string tableName, Vector embedding, int limit, double minRelevanceScore = 0, bool withEmbeddings = false,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
- var queryColumns = "key, metadata, timestamp";
+ string queryColumns = "key, metadata, timestamp";
if (withEmbeddings)
{
queryColumns = "*";
}
- using NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
+ NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
- using NpgsqlCommand cmd = connection.CreateCommand();
- cmd.CommandText = @$"
- SELECT * FROM (SELECT {queryColumns}, 1 - (embedding <=> @embedding) AS cosine_similarity FROM {this.GetTableName(collectionName)}
- ) AS sk_memory_cosine_similarity_table
- WHERE cosine_similarity >= @min_relevance_score
- ORDER BY cosine_similarity DESC
- Limit @limit";
- cmd.Parameters.AddWithValue("@embedding", embeddingFilter);
- cmd.Parameters.AddWithValue("@collection", collectionName);
- cmd.Parameters.AddWithValue("@min_relevance_score", minRelevanceScore);
- cmd.Parameters.AddWithValue("@limit", limit);
-
- using var dataReader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
-
- while (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false))
+ await using (connection)
{
- double cosineSimilarity = dataReader.GetDouble(dataReader.GetOrdinal("cosine_similarity"));
- yield return (await this.ReadEntryAsync(dataReader, withEmbeddings, cancellationToken).ConfigureAwait(false), cosineSimilarity);
+ using NpgsqlCommand cmd = connection.CreateCommand();
+ cmd.CommandText = @$"
+ SELECT * FROM (SELECT {queryColumns}, 1 - (embedding <=> @embedding) AS cosine_similarity FROM {this.GetFullTableName(tableName)}
+ ) AS sk_memory_cosine_similarity_table
+ WHERE cosine_similarity >= @min_relevance_score
+ ORDER BY cosine_similarity DESC
+ Limit @limit";
+ cmd.Parameters.AddWithValue("@embedding", embedding);
+ cmd.Parameters.AddWithValue("@collection", tableName);
+ cmd.Parameters.AddWithValue("@min_relevance_score", minRelevanceScore);
+ cmd.Parameters.AddWithValue("@limit", limit);
+
+ using NpgsqlDataReader dataReader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
+
+ while (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false))
+ {
+ double cosineSimilarity = dataReader.GetDouble(dataReader.GetOrdinal("cosine_similarity"));
+ yield return (await this.ReadEntryAsync(dataReader, withEmbeddings, cancellationToken).ConfigureAwait(false), cosineSimilarity);
+ }
}
}
- ///
- /// Read a entry by its key.
- ///
- /// The name assigned to a collection of entries.
- /// The key of the entry to read.
- /// If true, the embeddings will be returned in the entries.
- /// The to monitor for cancellation requests. The default is .
- ///
- public async Task ReadAsync(string collectionName, string key,
+ ///
+ public async Task ReadAsync(string tableName, string key,
bool withEmbeddings = false, CancellationToken cancellationToken = default)
{
- var queryColumns = "key, metadata, timestamp";
+ string queryColumns = "key, metadata, timestamp";
if (withEmbeddings)
{
queryColumns = "*";
}
- using NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
+ NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
- using NpgsqlCommand cmd = connection.CreateCommand();
- cmd.CommandText = $"SELECT {queryColumns} FROM {this.GetTableName(collectionName)} WHERE key=@key";
- cmd.Parameters.AddWithValue("@key", key);
-
- using var dataReader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
- if (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false))
+ await using (connection)
{
- return await this.ReadEntryAsync(dataReader, withEmbeddings, cancellationToken).ConfigureAwait(false);
- }
+ using NpgsqlCommand cmd = connection.CreateCommand();
+ cmd.CommandText = $"SELECT {queryColumns} FROM {this.GetFullTableName(tableName)} WHERE key=@key";
+ cmd.Parameters.AddWithValue("@key", key);
- return null;
+ using NpgsqlDataReader dataReader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
+ if (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false))
+ {
+ return await this.ReadEntryAsync(dataReader, withEmbeddings, cancellationToken).ConfigureAwait(false);
+ }
+
+ return null;
+ }
}
- ///
- /// Delete a entry by its key.
- ///
- /// The name assigned to a collection of entries.
- /// The key of the entry to delete.
- /// The to monitor for cancellation requests. The default is .
- ///
- public async Task DeleteAsync(string collectionName, string key, CancellationToken cancellationToken = default)
+ ///
+ public async IAsyncEnumerable ReadBatchAsync(string tableName, IEnumerable keys, bool withEmbeddings = false,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
- using NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
+ string[] keysArray = keys.ToArray();
+ if (keysArray.Length == 0)
+ {
+ yield break;
+ }
- using NpgsqlCommand cmd = connection.CreateCommand();
- cmd.CommandText = $"DELETE FROM {this.GetTableName(collectionName)} WHERE key=@key";
- cmd.Parameters.AddWithValue("@key", key);
+ string queryColumns = "key, metadata, timestamp";
+ if (withEmbeddings)
+ {
+ queryColumns = "*";
+ }
- await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
+ NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ await using (connection)
+ {
+ using NpgsqlCommand cmd = connection.CreateCommand();
+ cmd.CommandText = $"SELECT {queryColumns} FROM {this.GetFullTableName(tableName)} WHERE key=ANY(@keys)";
+ cmd.Parameters.AddWithValue("@keys", NpgsqlDbType.Array | NpgsqlDbType.Text, keysArray);
+
+ using NpgsqlDataReader dataReader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
+ while (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false))
+ {
+ yield return await this.ReadEntryAsync(dataReader, withEmbeddings, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+
+ ///
+ public async Task DeleteAsync(string tableName, string key, CancellationToken cancellationToken = default)
+ {
+ NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ await using (connection)
+ {
+ using NpgsqlCommand cmd = connection.CreateCommand();
+ cmd.CommandText = $"DELETE FROM {this.GetFullTableName(tableName)} WHERE key=@key";
+ cmd.Parameters.AddWithValue("@key", key);
+
+ await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ public async Task DeleteBatchAsync(string tableName, IEnumerable keys, CancellationToken cancellationToken = default)
+ {
+ string[] keysArray = keys.ToArray();
+ if (keysArray.Length == 0)
+ {
+ return;
+ }
+
+ NpgsqlConnection connection = await this._dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ await using (connection)
+ {
+ using NpgsqlCommand cmd = connection.CreateCommand();
+ cmd.CommandText = $"DELETE FROM {this.GetFullTableName(tableName)} WHERE key=ANY(@keys)";
+ cmd.Parameters.AddWithValue("@keys", NpgsqlDbType.Array | NpgsqlDbType.Text, keysArray);
+
+ await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
+ }
}
#region private ================================================================================
@@ -243,7 +274,6 @@ public class PostgresDbClient : IPostgresDbClient
private readonly NpgsqlDataSource _dataSource;
private readonly int _vectorSize;
private readonly string _schema;
- private readonly int _numberOfLists;
///
/// Read a entry.
@@ -257,54 +287,18 @@ public class PostgresDbClient : IPostgresDbClient
string key = dataReader.GetString(dataReader.GetOrdinal("key"));
string metadata = dataReader.GetString(dataReader.GetOrdinal("metadata"));
Vector? embedding = withEmbeddings ? await dataReader.GetFieldValueAsync(dataReader.GetOrdinal("embedding"), cancellationToken).ConfigureAwait(false) : null;
- long? timestamp = await dataReader.GetFieldValueAsync(dataReader.GetOrdinal("timestamp"), cancellationToken).ConfigureAwait(false);
+ DateTime? timestamp = await dataReader.GetFieldValueAsync(dataReader.GetOrdinal("timestamp"), cancellationToken).ConfigureAwait(false);
return new PostgresMemoryEntry() { Key = key, MetadataString = metadata, Embedding = embedding, Timestamp = timestamp };
}
///
- /// Create a collection as table.
+ /// Get full table name with schema from table name.
///
- /// An opened instance.
- /// The name assigned to a collection of entries.
- /// The to monitor for cancellation requests. The default is .
+ ///
///
- private async Task CreateTableAsync(NpgsqlConnection connection, string collectionName, CancellationToken cancellationToken = default)
+ private string GetFullTableName(string tableName)
{
- using NpgsqlCommand cmd = connection.CreateCommand();
- cmd.CommandText = $@"
- CREATE TABLE IF NOT EXISTS {this.GetTableName(collectionName)} (
- key TEXT NOT NULL,
- metadata JSONB,
- embedding vector({this._vectorSize}),
- timestamp BIGINT,
- PRIMARY KEY (key))";
- await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Create index for collection table.
- ///
- /// An opened instance.
- /// The name assigned to a collection of entries.
- /// The to monitor for cancellation requests. The default is .
- ///
- private async Task CreateIndexAsync(NpgsqlConnection connection, string collectionName, CancellationToken cancellationToken = default)
- {
- using NpgsqlCommand cmd = connection.CreateCommand();
- cmd.CommandText = $@"
- CREATE INDEX IF NOT EXISTS {collectionName}_ix
- ON {this.GetTableName(collectionName)} USING ivfflat (embedding vector_cosine_ops) WITH (lists = {this._numberOfLists})";
- await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
- }
-
- ///
- /// Get table name from collection name.
- ///
- ///
- ///
- private string GetTableName(string collectionName)
- {
- return $"{this._schema}.\"{collectionName}\"";
+ return $"{this._schema}.\"{tableName}\"";
}
#endregion
}
diff --git a/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresKernelBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresKernelBuilderExtensions.cs
index 440dfcd9dc..4baa6728c2 100644
--- a/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresKernelBuilderExtensions.cs
+++ b/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresKernelBuilderExtensions.cs
@@ -18,18 +18,16 @@ public static class PostgresKernelBuilderExtensions
/// The instance
/// Postgres data source.
/// Embedding vector size.
- /// Schema of collection tables.
- /// Specifies the number of lists for indexing. Higher values can improve recall but may impact performance. The default value is 1000. More info
+ /// Schema of collection tables. The default value is "public".
/// Self instance
public static KernelBuilder WithPostgresMemoryStore(this KernelBuilder builder,
NpgsqlDataSource dataSource,
int vectorSize,
- string schema = PostgresMemoryStore.DefaultSchema,
- int numberOfLists = PostgresMemoryStore.DefaultNumberOfLists)
+ string schema = PostgresMemoryStore.DefaultSchema)
{
builder.WithMemoryStorage((parameters) =>
{
- return new PostgresMemoryStore(dataSource, vectorSize, schema, numberOfLists);
+ return new PostgresMemoryStore(dataSource, vectorSize, schema);
});
return builder;
diff --git a/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryEntry.cs b/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryEntry.cs
index 29a4b8f31f..a7429b44c1 100644
--- a/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryEntry.cs
+++ b/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryEntry.cs
@@ -1,5 +1,6 @@
īģŋ// Copyright (c) Microsoft. All rights reserved.
+using System;
using Pgvector;
namespace Microsoft.SemanticKernel.Connectors.Memory.Postgres;
@@ -25,7 +26,7 @@ public record struct PostgresMemoryEntry
public Vector? Embedding { get; set; }
///
- /// Optional timestamp.
+ /// Optional timestamp. Its 'DateTimeKind' is
///
- public long? Timestamp { get; set; }
+ public DateTime? Timestamp { get; set; }
}
diff --git a/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryStore.cs b/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryStore.cs
index 269624651e..b95ac91f8f 100644
--- a/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryStore.cs
+++ b/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryStore.cs
@@ -1,6 +1,5 @@
īģŋ// Copyright (c) Microsoft. All rights reserved.
-using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -23,7 +22,6 @@ namespace Microsoft.SemanticKernel.Connectors.Memory.Postgres;
public class PostgresMemoryStore : IMemoryStore
{
internal const string DefaultSchema = "public";
- internal const int DefaultNumberOfLists = 1000;
///
/// Initializes a new instance of the class.
@@ -31,9 +29,8 @@ public class PostgresMemoryStore : IMemoryStore
/// Postgres data source.
/// Embedding vector size.
/// Database schema of collection tables. The default value is "public".
- /// Specifies the number of lists for indexing. Higher values can improve recall but may impact performance. The default value is 1000. More info
- public PostgresMemoryStore(NpgsqlDataSource dataSource, int vectorSize, string schema = DefaultSchema, int numberOfLists = DefaultNumberOfLists)
- : this(new PostgresDbClient(dataSource, schema, vectorSize, numberOfLists))
+ public PostgresMemoryStore(NpgsqlDataSource dataSource, int vectorSize, string schema = DefaultSchema)
+ : this(new PostgresDbClient(dataSource, schema, vectorSize))
{
}
@@ -47,7 +44,7 @@ public class PostgresMemoryStore : IMemoryStore
{
Verify.NotNullOrWhiteSpace(collectionName);
- await this._postgresDbClient.CreateCollectionAsync(collectionName, cancellationToken).ConfigureAwait(false);
+ await this._postgresDbClient.CreateTableAsync(collectionName, cancellationToken).ConfigureAwait(false);
}
///
@@ -55,13 +52,13 @@ public class PostgresMemoryStore : IMemoryStore
{
Verify.NotNullOrWhiteSpace(collectionName);
- return await this._postgresDbClient.DoesCollectionExistsAsync(collectionName, cancellationToken).ConfigureAwait(false);
+ return await this._postgresDbClient.DoesTableExistsAsync(collectionName, cancellationToken).ConfigureAwait(false);
}
///
public async IAsyncEnumerable GetCollectionsAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
- await foreach (var collection in this._postgresDbClient.GetCollectionsAsync(cancellationToken).ConfigureAwait(false))
+ await foreach (string collection in this._postgresDbClient.GetTablesAsync(cancellationToken).ConfigureAwait(false))
{
yield return collection;
}
@@ -72,7 +69,7 @@ public class PostgresMemoryStore : IMemoryStore
{
Verify.NotNullOrWhiteSpace(collectionName);
- await this._postgresDbClient.DeleteCollectionAsync(collectionName, cancellationToken).ConfigureAwait(false);
+ await this._postgresDbClient.DeleteTableAsync(collectionName, cancellationToken).ConfigureAwait(false);
}
///
@@ -89,7 +86,7 @@ public class PostgresMemoryStore : IMemoryStore
{
Verify.NotNullOrWhiteSpace(collectionName);
- foreach (var record in records)
+ foreach (MemoryRecord record in records)
{
yield return await this.InternalUpsertAsync(collectionName, record, cancellationToken).ConfigureAwait(false);
}
@@ -100,7 +97,11 @@ public class PostgresMemoryStore : IMemoryStore
{
Verify.NotNullOrWhiteSpace(collectionName);
- return await this.InternalGetAsync(collectionName, key, withEmbedding, cancellationToken).ConfigureAwait(false);
+ PostgresMemoryEntry? entry = await this._postgresDbClient.ReadAsync(collectionName, key, withEmbedding, cancellationToken).ConfigureAwait(false);
+
+ if (!entry.HasValue) { return null; }
+
+ return this.GetMemoryRecordFromEntry(entry.Value);
}
///
@@ -109,13 +110,9 @@ public class PostgresMemoryStore : IMemoryStore
{
Verify.NotNullOrWhiteSpace(collectionName);
- foreach (var key in keys)
+ await foreach (PostgresMemoryEntry entry in this._postgresDbClient.ReadBatchAsync(collectionName, keys, withEmbeddings, cancellationToken).ConfigureAwait(false))
{
- var result = await this.InternalGetAsync(collectionName, key, withEmbeddings, cancellationToken).ConfigureAwait(false);
- if (result != null)
- {
- yield return result;
- }
+ yield return this.GetMemoryRecordFromEntry(entry);
}
}
@@ -132,10 +129,7 @@ public class PostgresMemoryStore : IMemoryStore
{
Verify.NotNullOrWhiteSpace(collectionName);
- foreach (var key in keys)
- {
- await this._postgresDbClient.DeleteAsync(collectionName, key, cancellationToken).ConfigureAwait(false);
- }
+ await this._postgresDbClient.DeleteBatchAsync(collectionName, keys, cancellationToken).ConfigureAwait(false);
}
///
@@ -155,21 +149,16 @@ public class PostgresMemoryStore : IMemoryStore
}
IAsyncEnumerable<(PostgresMemoryEntry, double)> results = this._postgresDbClient.GetNearestMatchesAsync(
- collectionName: collectionName,
- embeddingFilter: new Vector(embedding.Vector.ToArray()),
+ tableName: collectionName,
+ embedding: new Vector(embedding.Vector.ToArray()),
limit: limit,
minRelevanceScore: minRelevanceScore,
withEmbeddings: withEmbeddings,
cancellationToken: cancellationToken);
- await foreach (var (entry, cosineSimilarity) in results.ConfigureAwait(false))
+ await foreach ((PostgresMemoryEntry entry, double cosineSimilarity) in results.ConfigureAwait(false))
{
- MemoryRecord record = MemoryRecord.FromJsonMetadata(
- json: entry.MetadataString,
- this.GetEmbeddingForEntry(entry),
- entry.Key,
- ParseTimestamp(entry.Timestamp));
- yield return (record, cosineSimilarity);
+ yield return (this.GetMemoryRecordFromEntry(entry), cosineSimilarity);
}
}
@@ -190,52 +179,28 @@ public class PostgresMemoryStore : IMemoryStore
private readonly IPostgresDbClient _postgresDbClient;
- private static long? ToTimestampLong(DateTimeOffset? timestamp)
- {
- return timestamp?.ToUnixTimeMilliseconds();
- }
-
- private static DateTimeOffset? ParseTimestamp(long? timestamp)
- {
- if (timestamp.HasValue)
- {
- return DateTimeOffset.FromUnixTimeMilliseconds(timestamp.Value);
- }
-
- return null;
- }
-
private async Task InternalUpsertAsync(string collectionName, MemoryRecord record, CancellationToken cancellationToken)
{
record.Key = record.Metadata.Id;
await this._postgresDbClient.UpsertAsync(
- collectionName: collectionName,
+ tableName: collectionName,
key: record.Key,
metadata: record.GetSerializedMetadata(),
embedding: new Vector(record.Embedding.Vector.ToArray()),
- timestamp: ToTimestampLong(record.Timestamp),
+ timestamp: record.Timestamp?.UtcDateTime,
cancellationToken: cancellationToken).ConfigureAwait(false);
return record.Key;
}
- private async Task InternalGetAsync(string collectionName, string key, bool withEmbedding, CancellationToken cancellationToken)
+ private MemoryRecord GetMemoryRecordFromEntry(PostgresMemoryEntry entry)
{
- PostgresMemoryEntry? entry = await this._postgresDbClient.ReadAsync(collectionName, key, withEmbedding, cancellationToken).ConfigureAwait(false);
-
- if (!entry.HasValue) { return null; }
-
return MemoryRecord.FromJsonMetadata(
- json: entry.Value.MetadataString,
- embedding: this.GetEmbeddingForEntry(entry.Value),
- entry.Value.Key,
- ParseTimestamp(entry.Value.Timestamp));
- }
-
- private Embedding GetEmbeddingForEntry(PostgresMemoryEntry entry)
- {
- return entry.Embedding != null ? new Embedding(entry.Embedding!.ToArray()) : Embedding.Empty;
+ json: entry.MetadataString,
+ embedding: entry.Embedding != null ? new Embedding(entry.Embedding!.ToArray()) : Embedding.Empty,
+ key: entry.Key,
+ timestamp: entry.Timestamp?.ToLocalTime());
}
#endregion
diff --git a/dotnet/src/Connectors/Connectors.Memory.Postgres/README.md b/dotnet/src/Connectors/Connectors.Memory.Postgres/README.md
index 4ba3424224..0dd94e0536 100644
--- a/dotnet/src/Connectors/Connectors.Memory.Postgres/README.md
+++ b/dotnet/src/Connectors/Connectors.Memory.Postgres/README.md
@@ -35,20 +35,57 @@ sk_demo=# CREATE EXTENSION vector;
3. To use Postgres as a semantic memory store:
```csharp
-NpgsqlDataSourceBuilder dataSourceBuilder = new NpgsqlDataSourceBuilder("Host=localhost;Port=5432;Database=sk_memory;User Id=postgres;Password=mysecretpassword");
+NpgsqlDataSourceBuilder dataSourceBuilder = new NpgsqlDataSourceBuilder("Host=localhost;Port=5432;Database=sk_demo;User Id=postgres;Password=mysecretpassword");
dataSourceBuilder.UseVector();
NpgsqlDataSource dataSource = dataSourceBuilder.Build();
-PostgresMemoryStore memoryStore = new PostgresMemoryStore(dataSource, vectorSize: 1536/*, schema: "public", numberOfLists: 1000 */);
+PostgresMemoryStore memoryStore = new PostgresMemoryStore(dataSource, vectorSize: 1536/*, schema: "public" */);
IKernel kernel = Kernel.Builder
.WithLogger(ConsoleLogger.Log)
.WithOpenAITextEmbeddingGenerationService("text-embedding-ada-002", Env.Var("OPENAI_API_KEY"))
.WithMemoryStorage(memoryStore)
- //.WithPostgresMemoryStore(dataSource, vectorSize: 1536, schema: "public", numberOfLists: 1000) // This method offers an alternative approach to registering Postgres memory store.
+ //.WithPostgresMemoryStore(dataSource, vectorSize: 1536, schema: "public") // This method offers an alternative approach to registering Postgres memory store.
.Build();
```
+### Create Index
+
+> By default, pgvector performs exact nearest neighbor search, which provides perfect recall.
+
+> You can add an index to use approximate nearest neighbor search, which trades some recall for performance. Unlike typical indexes, you will see different results for queries after adding an approximate index.
+
+> Three keys to achieving good recall are:
+> - Create the index after the table has some data
+> - Choose an appropriate number of lists - a good place to start is rows / 1000 for up to 1M rows and sqrt(rows) for over 1M rows
+> - When querying, specify an appropriate number of probes (higher is better for recall, lower is better for speed) - a good place to start is sqrt(lists)
+
+Please read [the documentation](https://github.com/pgvector/pgvector#indexing) for more information.
+
+Based on the data rows of your collection table, consider the following statement to create an index.
+
+```sql
+DO $$
+DECLARE
+ collection TEXT;
+ c_count INTEGER;
+BEGIN
+ SELECT 'REPLACE YOUR COLLECTION TABLE NAME' INTO collection;
+
+ -- Get count of records in collection
+ EXECUTE format('SELECT count(*) FROM public.%I;', collection) INTO c_count;
+
+ -- Create Index (https://github.com/pgvector/pgvector#indexing)
+ IF c_count > 10000000 THEN
+ EXECUTE format('CREATE INDEX %I ON public.%I USING ivfflat (embedding vector_cosine_ops) WITH (lists = %s);',
+ collection || '_ix', collection, ROUND(sqrt(c_count)));
+ ELSIF c_count > 10000 THEN
+ EXECUTE format('CREATE INDEX %I ON public.%I USING ivfflat (embedding vector_cosine_ops) WITH (lists = %s);',
+ collection || '_ix', collection, c_count / 1000);
+ END IF;
+END $$;
+```
+
## Migration from older versions
Since Postgres Memory connector has been re-implemented, the new implementation uses a separate table to store each Collection.
@@ -64,6 +101,7 @@ We provide the following migration script to help you migrate to the new structu
DO $$
DECLARE
r record;
+ c_count integer;
BEGIN
FOR r IN SELECT DISTINCT collection FROM sk_memory_table LOOP
@@ -75,14 +113,23 @@ BEGIN
key TEXT NOT NULL,
metadata JSONB,
embedding vector(1536),
- timestamp BIGINT,
+ timestamp TIMESTAMP WITH TIME ZONE,
PRIMARY KEY (key)
);', r.collection);
-
- -- Create Index (You can modify the size of lists according to your data needs. Its default value is 1000.)
- EXECUTE format('CREATE INDEX %I
- ON public.%I USING ivfflat (embedding vector_cosine_ops) WITH (lists = 1000);',
- r.collection || '_ix', r.collection);
+
+ -- Get count of records in collection
+ SELECT count(*) INTO c_count FROM sk_memory_table WHERE collection = r.collection AND key <> '';
+
+ -- Create Index (https://github.com/pgvector/pgvector#indexing)
+ IF c_count > 10000000 THEN
+ EXECUTE format('CREATE INDEX %I
+ ON public.%I USING ivfflat (embedding vector_cosine_ops) WITH (lists = %s);',
+ r.collection || '_ix', r.collection, ROUND(sqrt(c_count)));
+ ELSIF c_count > 10000 THEN
+ EXECUTE format('CREATE INDEX %I
+ ON public.%I USING ivfflat (embedding vector_cosine_ops) WITH (lists = %s);',
+ r.collection || '_ix', r.collection, c_count / 1000);
+ END IF;
END LOOP;
END $$;
@@ -93,7 +140,7 @@ DECLARE
BEGIN
FOR r IN SELECT DISTINCT collection FROM sk_memory_table LOOP
EXECUTE format('INSERT INTO public.%I (key, metadata, embedding, timestamp)
- SELECT key, metadata::JSONB, embedding, timestamp
+ SELECT key, metadata::JSONB, embedding, to_timestamp(timestamp / 1000.0) AT TIME ZONE ''UTC''
FROM sk_memory_table WHERE collection = %L AND key <> '''';', r.collection, r.collection);
END LOOP;
END $$;
diff --git a/dotnet/src/Connectors/Connectors.Memory.Redis/README.md b/dotnet/src/Connectors/Connectors.Memory.Redis/README.md
index 7e460c79af..4d7c62bbe2 100644
--- a/dotnet/src/Connectors/Connectors.Memory.Redis/README.md
+++ b/dotnet/src/Connectors/Connectors.Memory.Redis/README.md
@@ -1,13 +1,17 @@
īģŋ# Microsoft.SemanticKernel.Connectors.Memory.Redis
-This connector uses Redis to implement Semantic Memory. It requires the [RediSearch](https://redis.io/docs/stack/search) module to be enabled on Redis to implement vector similarity search.
+This connector uses Redis to implement Semantic Memory. It requires the [RediSearch](https://redis.io/docs/interact/search-and-query/) module to be enabled on Redis to implement vector similarity search.
## What is RediSearch?
-[RediSearch](https://redis.io/docs/stack/search) is a source-available Redis module that enables querying, secondary indexing, and full-text search for Redis. These features enable multi-field queries, aggregation, exact phrase matching, numeric filtering, geo filtering and vector similarity semantic search on top of text queries.
+[RediSearch](https://redis.io/docs/interact/search-and-query/) is a source-available Redis module that enables querying, secondary indexing, and full-text search for Redis. These features enable multi-field queries, aggregation, exact phrase matching, numeric filtering, geo filtering and vector similarity semantic search on top of text queries.
-How to set up the RediSearch, please refer to its [documentation](https://redis.io/docs/stack/search/quick_start/).
-Or use the [Redis Enterprise](https://redis.io/docs/about/redis-enterprise/), see [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/garantiadata.redis_enterprise_1sp_public_preview?tab=Overview), [AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-e6y7ork67pjwg?sr=0-2&ref_=beagle&applicationId=AWSMPContessa), or [Google Marketplace](https://console.cloud.google.com/marketplace/details/redislabs-public/redis-enterprise?pli=1).
+Ways to get RediSearch:
+1. You can create an [Azure Cache for Redis Enterpise instance](https://learn.microsoft.com/azure/azure-cache-for-redis/quickstart-create-redis-enterprise) and [enable RediSearch module](https://learn.microsoft.com/azure/azure-cache-for-redis/cache-redis-modules).
+
+1. Set up the RediSearch on your self-managed Redis, please refer to its [documentation](https://redis.io/docs/interact/search-and-query/quickstart/).
+
+1. Use the [Redis Enterprise](https://redis.io/docs/about/redis-enterprise/), see [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/garantiadata.redis_enterprise_1sp_public_preview?tab=Overview), [AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-e6y7ork67pjwg?sr=0-2&ref_=beagle&applicationId=AWSMPContessa), or [Google Marketplace](https://console.cloud.google.com/marketplace/details/redislabs-public/redis-enterprise?pli=1).
## Quick start
diff --git a/dotnet/src/Connectors/Connectors.UnitTests/Connectors.UnitTests.csproj b/dotnet/src/Connectors/Connectors.UnitTests/Connectors.UnitTests.csproj
index 2174e8393a..8e39171e63 100644
--- a/dotnet/src/Connectors/Connectors.UnitTests/Connectors.UnitTests.csproj
+++ b/dotnet/src/Connectors/Connectors.UnitTests/Connectors.UnitTests.csproj
@@ -20,6 +20,10 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
+
+
+
+
diff --git a/dotnet/src/Connectors/Connectors.UnitTests/Memory/Postgres/PostgresMemoryStoreTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/Memory/Postgres/PostgresMemoryStoreTests.cs
index 581d623a0c..e358466ed9 100644
--- a/dotnet/src/Connectors/Connectors.UnitTests/Memory/Postgres/PostgresMemoryStoreTests.cs
+++ b/dotnet/src/Connectors/Connectors.UnitTests/Memory/Postgres/PostgresMemoryStoreTests.cs
@@ -26,7 +26,7 @@ public class PostgresMemoryStoreTests
{
this._postgresDbClientMock = new Mock();
this._postgresDbClientMock
- .Setup(client => client.DoesCollectionExistsAsync(CollectionName, CancellationToken.None))
+ .Setup(client => client.DoesTableExistsAsync(CollectionName, CancellationToken.None))
.ReturnsAsync(true);
}
@@ -40,7 +40,7 @@ public class PostgresMemoryStoreTests
await store.CreateCollectionAsync(CollectionName);
// Assert
- this._postgresDbClientMock.Verify(client => client.CreateCollectionAsync(CollectionName, CancellationToken.None), Times.Once());
+ this._postgresDbClientMock.Verify(client => client.CreateTableAsync(CollectionName, CancellationToken.None), Times.Once());
}
[Fact]
@@ -53,7 +53,7 @@ public class PostgresMemoryStoreTests
await store.DeleteCollectionAsync(CollectionName);
// Assert
- this._postgresDbClientMock.Verify(client => client.DeleteCollectionAsync(CollectionName, CancellationToken.None), Times.Once());
+ this._postgresDbClientMock.Verify(client => client.DeleteTableAsync(CollectionName, CancellationToken.None), Times.Once());
}
[Fact]
@@ -76,7 +76,7 @@ public class PostgresMemoryStoreTests
const string collectionName = "non-existent-collection";
this._postgresDbClientMock
- .Setup(client => client.DoesCollectionExistsAsync(collectionName, CancellationToken.None))
+ .Setup(client => client.DoesTableExistsAsync(collectionName, CancellationToken.None))
.ReturnsAsync(false);
var store = new PostgresMemoryStore(this._postgresDbClientMock.Object);
@@ -192,12 +192,13 @@ public class PostgresMemoryStoreTests
.ReturnsAsync(this.GetPostgresMemoryEntryFromMemoryRecord(memoryRecord));
}
- var doesNotExistMemoryKey = "fake-record-key";
- this._postgresDbClientMock
- .Setup(client => client.ReadAsync(CollectionName, doesNotExistMemoryKey, true, CancellationToken.None))
- .ReturnsAsync((PostgresMemoryEntry?)null);
+ memoryRecordKeys.Insert(0, "non-existent-record-key-1");
+ memoryRecordKeys.Insert(2, "non-existent-record-key-2");
+ memoryRecordKeys.Add("non-existent-record-key-3");
- memoryRecordKeys.Add(doesNotExistMemoryKey);
+ this._postgresDbClientMock
+ .Setup(client => client.ReadBatchAsync(CollectionName, memoryRecordKeys, true, CancellationToken.None))
+ .Returns(expectedMemoryRecords.Select(memoryRecord => this.GetPostgresMemoryEntryFromMemoryRecord(memoryRecord)).ToAsyncEnumerable());
var store = new PostgresMemoryStore(this._postgresDbClientMock.Object);
@@ -205,6 +206,7 @@ public class PostgresMemoryStoreTests
var actualMemoryRecords = await store.GetBatchAsync(CollectionName, memoryRecordKeys, withEmbeddings: true).ToListAsync();
// Assert
+ this._postgresDbClientMock.Verify(client => client.ReadBatchAsync(CollectionName, memoryRecordKeys, true, CancellationToken.None), Times.Once());
Assert.Equal(expectedMemoryRecords.Length, actualMemoryRecords.Count);
for (var i = 0; i < expectedMemoryRecords.Length; i++)
@@ -220,7 +222,7 @@ public class PostgresMemoryStoreTests
var expectedCollections = new List { "fake-collection-1", "fake-collection-2", "fake-collection-3" };
this._postgresDbClientMock
- .Setup(client => client.GetCollectionsAsync(CancellationToken.None))
+ .Setup(client => client.GetTablesAsync(CancellationToken.None))
.Returns(expectedCollections.ToAsyncEnumerable());
var store = new PostgresMemoryStore(this._postgresDbClientMock.Object);
@@ -262,10 +264,7 @@ public class PostgresMemoryStoreTests
await store.RemoveBatchAsync(CollectionName, memoryRecordKeys);
// Assert
- foreach (var memoryRecordKey in memoryRecordKeys)
- {
- this._postgresDbClientMock.Verify(client => client.DeleteAsync(CollectionName, memoryRecordKey, CancellationToken.None), Times.Once());
- }
+ this._postgresDbClientMock.Verify(client => client.DeleteBatchAsync(CollectionName, memoryRecordKeys, CancellationToken.None), Times.Once());
}
#region private ================================================================================
@@ -293,7 +292,8 @@ public class PostgresMemoryStoreTests
description: "description-" + Guid.NewGuid().ToString(),
embedding: memoryEmbedding,
additionalMetadata: "metadata-" + Guid.NewGuid().ToString(),
- key: id);
+ key: id,
+ timestamp: DateTimeOffset.Now);
}
private PostgresMemoryEntry GetPostgresMemoryEntryFromMemoryRecord(MemoryRecord memoryRecord)
@@ -303,7 +303,7 @@ public class PostgresMemoryStoreTests
Key = memoryRecord.Key,
Embedding = new Pgvector.Vector(memoryRecord.Embedding.Vector.ToArray()),
MetadataString = memoryRecord.GetSerializedMetadata(),
- Timestamp = memoryRecord.Timestamp?.ToUnixTimeMilliseconds()
+ Timestamp = memoryRecord.Timestamp?.UtcDateTime
};
}
diff --git a/dotnet/src/Extensions/Extensions.UnitTests/Planning/ActionPlanner/ActionPlannerTests.cs b/dotnet/src/Extensions/Extensions.UnitTests/Planning/ActionPlanner/ActionPlannerTests.cs
new file mode 100644
index 0000000000..41c2c8fee7
--- /dev/null
+++ b/dotnet/src/Extensions/Extensions.UnitTests/Planning/ActionPlanner/ActionPlannerTests.cs
@@ -0,0 +1,161 @@
+īģŋ// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.AI.TextCompletion;
+using Microsoft.SemanticKernel.Memory;
+using Microsoft.SemanticKernel.Orchestration;
+using Microsoft.SemanticKernel.Planning;
+using Microsoft.SemanticKernel.SemanticFunctions;
+using Microsoft.SemanticKernel.SkillDefinition;
+using Moq;
+using Xunit;
+
+namespace SemanticKernel.Extensions.UnitTests.Planning.ActionPlanner;
+
+public sealed class ActionPlannerTests
+{
+ [Fact]
+ public async Task ExtractsAndDeserializesWellFormedJsonFromPlannerResult()
+ {
+ // Arrange
+ var functions = new List<(string name, string skillName, string description, bool isSemantic)>()
+ {
+ ("SendEmail", "email", "Send an e-mail", false),
+ ("PullsList", "GitHubSkill", "List pull requests", true)
+ };
+
+ var functionsView = new FunctionsView();
+ var skills = new Mock();
+ foreach (var (name, skillName, description, isSemantic) in functions)
+ {
+ var functionView = new FunctionView(name, skillName, description, new List(), isSemantic, true);
+ var mockFunction = CreateMockFunction(functionView);
+ functionsView.AddFunction(functionView);
+
+ mockFunction.Setup(x =>
+ x.InvokeAsync(It.IsAny(), It.IsAny()))
+ .Returns((context, settings) =>
+ {
+ context.Variables.Update("MOCK FUNCTION CALLED");
+ return Task.FromResult(context);
+ });
+
+ skills.Setup(x => x.GetFunction(It.Is(s => s == skillName), It.Is(s => s == name)))
+ .Returns(mockFunction.Object);
+ ISKFunction? outFunc = mockFunction.Object;
+ skills.Setup(x => x.TryGetFunction(It.Is(s => s == skillName), It.Is(s => s == name), out outFunc)).Returns(true);
+ }
+
+ skills.Setup(x => x.GetFunctionsView(It.IsAny(), It.IsAny())).Returns(functionsView);
+
+ string planString = "Here is a possible plan to accomplish the user intent:\n\n{\"plan\":{\n\"rationale\": \"the list contains a function that allows to list pull requests\",\n\"function\": \"GitHubSkill.PullsList\",\n\"parameters\": {\n\"owner\": \"microsoft\",\n\"repo\": \"semantic-kernel\",\n\"state\": \"open\"\n}}}\n\nThis plan uses the `GitHubSkill.PullsList` function to list the open pull requests for the `semantic-kernel` repository owned by `microsoft`. The `state` parameter is set to `\"open\"` to filter the results to only show open pull requests.";
+
+ var kernel = this.CreateMockKernelAndFunctionFlowWithTestString(planString, skills);
+
+ var planner = new Microsoft.SemanticKernel.Planning.ActionPlanner(kernel.Object);
+
+ // Act
+ var plan = await planner.CreatePlanAsync("goal");
+
+ // Assert
+ Assert.Equal("goal", plan.Description);
+
+ Assert.Equal(plan.Steps.Count, 1);
+ Assert.Equal(plan.Steps[0].SkillName, "GitHubSkill");
+ Assert.Equal(plan.Steps[0].Name, "PullsList");
+ }
+
+ [Fact]
+ public async Task InvalidJsonThrowsAsync()
+ {
+ // Arrange
+ string invalidJsonString = "<>";
+
+ var kernel = this.CreateMockKernelAndFunctionFlowWithTestString(invalidJsonString);
+
+ var planner = new Microsoft.SemanticKernel.Planning.ActionPlanner(kernel.Object);
+
+ // Act & Assert
+ await Assert.ThrowsAsync(async () => await planner.CreatePlanAsync("goal"));
+ }
+
+ [Fact]
+ public async Task MalformedJsonThrowsAsync()
+ {
+ // Arrange
+
+ // Extra opening brace before rationale
+ string invalidJsonString = "Here is a possible plan to accomplish the user intent:\n\n{\"plan\": { {\n\"rationale\": \"the list contains a function that allows to list pull requests\",\n\"function\": \"GitHubSkill.PullsList\",\n\"parameters\": {\n\"owner\": \"microsoft\",\n\"repo\": \"semantic-kernel\",\n\"state\": \"open\"\n}}}\n\nThis plan uses the `GitHubSkill.PullsList` function to list the open pull requests for the `semantic-kernel` repository owned by `microsoft`. The `state` parameter is set to `\"open\"` to filter the results to only show open pull requests.";
+
+ var kernel = this.CreateMockKernelAndFunctionFlowWithTestString(invalidJsonString);
+
+ var planner = new Microsoft.SemanticKernel.Planning.ActionPlanner(kernel.Object);
+
+ // Act & Assert
+ await Assert.ThrowsAsync(async () => await planner.CreatePlanAsync("goal"));
+ }
+
+ private Mock CreateMockKernelAndFunctionFlowWithTestString(string testPlanString, Mock? mockSkills = null)
+ {
+ var kernel = new Mock();
+
+ var memory = new Mock();
+ var skills = mockSkills;
+
+ if (mockSkills == null)
+ {
+ skills = new Mock();
+
+ var functionsView = new FunctionsView();
+ skills.Setup(x => x.GetFunctionsView(It.IsAny(), It.IsAny())).Returns(functionsView);
+ }
+
+ var returnContext = new SKContext(
+ new ContextVariables(testPlanString),
+ memory.Object,
+ skills!.Object,
+ new Mock().Object
+ );
+
+ var context = new SKContext(
+ new ContextVariables(),
+ memory.Object,
+ skills!.Object,
+ new Mock().Object
+ );
+
+ var mockFunctionFlowFunction = new Mock();
+ mockFunctionFlowFunction.Setup(x => x.InvokeAsync(
+ It.IsAny(),
+ null
+ )).Callback(
+ (c, s) => c.Variables.Update("Hello world!")
+ ).Returns(() => Task.FromResult(returnContext));
+
+ // Mock Skills
+ kernel.Setup(x => x.Skills).Returns(skills.Object);
+ kernel.Setup(x => x.CreateNewContext(It.IsAny())).Returns(context);
+
+ kernel.Setup(x => x.RegisterSemanticFunction(
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny()
+ )).Returns(mockFunctionFlowFunction.Object);
+
+ return kernel;
+ }
+
+ // Method to create Mock objects
+ private static Mock CreateMockFunction(FunctionView functionView)
+ {
+ var mockFunction = new Mock();
+ mockFunction.Setup(x => x.Describe()).Returns(functionView);
+ mockFunction.Setup(x => x.Name).Returns(functionView.Name);
+ mockFunction.Setup(x => x.SkillName).Returns(functionView.SkillName);
+ return mockFunction;
+ }
+}
diff --git a/dotnet/src/Extensions/Extensions.UnitTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs b/dotnet/src/Extensions/Extensions.UnitTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs
index 9291a93857..dd362521a8 100644
--- a/dotnet/src/Extensions/Extensions.UnitTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs
+++ b/dotnet/src/Extensions/Extensions.UnitTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs
@@ -8,7 +8,6 @@ using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Planning;
using Microsoft.SemanticKernel.Planning.Sequential;
-using Microsoft.SemanticKernel.Security;
using Microsoft.SemanticKernel.SemanticFunctions;
using Microsoft.SemanticKernel.SkillDefinition;
using Moq;
@@ -86,8 +85,7 @@ public class SequentialPlanParserTests
kernelMock.Setup(x => x.RegisterSemanticFunction(
It.IsAny(),
It.IsAny(),
- It.IsAny(),
- It.IsAny()
+ It.IsAny()
)).Returns(mockFunction.Object);
}
else
diff --git a/dotnet/src/Extensions/Extensions.UnitTests/Planning/SequentialPlanner/SequentialPlannerTests.cs b/dotnet/src/Extensions/Extensions.UnitTests/Planning/SequentialPlanner/SequentialPlannerTests.cs
index 22e8648e16..440938444f 100644
--- a/dotnet/src/Extensions/Extensions.UnitTests/Planning/SequentialPlanner/SequentialPlannerTests.cs
+++ b/dotnet/src/Extensions/Extensions.UnitTests/Planning/SequentialPlanner/SequentialPlannerTests.cs
@@ -10,7 +10,6 @@ using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Planning;
-using Microsoft.SemanticKernel.Security;
using Microsoft.SemanticKernel.SemanticFunctions;
using Microsoft.SemanticKernel.SkillDefinition;
using Moq;
@@ -104,8 +103,7 @@ public sealed class SequentialPlannerTests
kernel.Setup(x => x.RegisterSemanticFunction(
It.IsAny(),
It.IsAny(),
- It.IsAny(),
- It.IsAny()
+ It.IsAny()
)).Returns(mockFunctionFlowFunction.Object);
var planner = new Microsoft.SemanticKernel.Planning.SequentialPlanner(kernel.Object);
@@ -192,8 +190,7 @@ public sealed class SequentialPlannerTests
kernel.Setup(x => x.RegisterSemanticFunction(
It.IsAny(),
It.IsAny(),
- It.IsAny(),
- It.IsAny()
+ It.IsAny()
)).Returns(mockFunctionFlowFunction.Object);
var planner = new Microsoft.SemanticKernel.Planning.SequentialPlanner(kernel.Object);
diff --git a/dotnet/src/Extensions/Planning.ActionPlanner/ActionPlanner.cs b/dotnet/src/Extensions/Planning.ActionPlanner/ActionPlanner.cs
index e776a85d73..bba382bd2c 100644
--- a/dotnet/src/Extensions/Planning.ActionPlanner/ActionPlanner.cs
+++ b/dotnet/src/Extensions/Planning.ActionPlanner/ActionPlanner.cs
@@ -6,6 +6,7 @@ using System.ComponentModel;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
@@ -81,23 +82,7 @@ public sealed class ActionPlanner
this._context.Variables.Update(goal);
SKContext result = await this._plannerFunction.InvokeAsync(this._context).ConfigureAwait(false);
-
- ActionPlanResponse? planData;
- try
- {
- planData = JsonSerializer.Deserialize(result.ToString(), new JsonSerializerOptions
- {
- AllowTrailingCommas = true,
- DictionaryKeyPolicy = null,
- DefaultIgnoreCondition = JsonIgnoreCondition.Never,
- PropertyNameCaseInsensitive = true,
- });
- }
- catch (Exception e)
- {
- throw new PlanningException(PlanningException.ErrorCodes.InvalidPlan,
- "Plan parsing error, invalid JSON", e);
- }
+ ActionPlanResponse? planData = this.ParsePlannerResult(result);
if (planData == null)
{
@@ -226,6 +211,44 @@ Goal: tell me a joke.
";
}
+ #region private ================================================================================
+
+ ///
+ /// Native function that filters out good JSON from planner result in case additional text is present
+ /// using a similar regex to the balancing group regex defined here: https://learn.microsoft.com/en-us/dotnet/standard/base-types/grouping-constructs-in-regular-expressions#balancing-group-definitions
+ ///
+ /// Result context of planner function.
+ /// Instance of object deserialized from extracted JSON.
+ private ActionPlanResponse? ParsePlannerResult(SKContext plannerResult)
+ {
+ Regex planRegex = new("^[^{}]*(((?'Open'{)[^{}]*)+((?'Close-Open'})[^{}]*)+)*(?(Open)(?!))", RegexOptions.Singleline);
+ Match match = planRegex.Match(plannerResult.ToString());
+
+ if (match.Success && match.Groups["Close"].Length > 0)
+ {
+ string planJson = $"{{{match.Groups["Close"]}}}";
+ try
+ {
+ return JsonSerializer.Deserialize(planJson, new JsonSerializerOptions
+ {
+ AllowTrailingCommas = true,
+ DictionaryKeyPolicy = null,
+ DefaultIgnoreCondition = JsonIgnoreCondition.Never,
+ PropertyNameCaseInsensitive = true,
+ });
+ }
+ catch (Exception e)
+ {
+ throw new PlanningException(PlanningException.ErrorCodes.InvalidPlan,
+ "Plan parsing error, invalid JSON", e);
+ }
+ }
+ else
+ {
+ throw new PlanningException(PlanningException.ErrorCodes.InvalidPlan, $"Failed to extract valid json string from planner result: '{plannerResult}'");
+ }
+ }
+
private void PopulateList(StringBuilder list, IDictionary> functions)
{
foreach (KeyValuePair> skill in functions)
@@ -264,4 +287,6 @@ Goal: tell me a joke.
{
return x.EndsWith(".", StringComparison.Ordinal) ? x : $"{x}.";
}
+
+ #endregion
}
diff --git a/dotnet/src/Extensions/Planning.SequentialPlanner/SequentialPlannerConfig.cs b/dotnet/src/Extensions/Planning.SequentialPlanner/SequentialPlannerConfig.cs
index 3d5f02f739..ff21da4e77 100644
--- a/dotnet/src/Extensions/Planning.SequentialPlanner/SequentialPlannerConfig.cs
+++ b/dotnet/src/Extensions/Planning.SequentialPlanner/SequentialPlannerConfig.cs
@@ -52,7 +52,7 @@ public sealed class SequentialPlannerConfig
///
/// The maximum number of tokens to allow in a plan.
///
- public int MaxTokens { get; set; } = 1024;
+ public int? MaxTokens { get; set; }
///
/// Whether to allow missing functions in the plan on creation.
diff --git a/dotnet/src/IntegrationTests/IntegrationTests.csproj b/dotnet/src/IntegrationTests/IntegrationTests.csproj
index 56a65c3612..28efab76da 100644
--- a/dotnet/src/IntegrationTests/IntegrationTests.csproj
+++ b/dotnet/src/IntegrationTests/IntegrationTests.csproj
@@ -27,6 +27,10 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
+
+
+
+
diff --git a/dotnet/src/IntegrationTests/Planning/PlanTests.cs b/dotnet/src/IntegrationTests/Planning/PlanTests.cs
index 757436e789..ff885ffea8 100644
--- a/dotnet/src/IntegrationTests/Planning/PlanTests.cs
+++ b/dotnet/src/IntegrationTests/Planning/PlanTests.cs
@@ -146,7 +146,6 @@ public sealed class PlanTests : IDisposable
}
[Theory]
- [InlineData(null, "Write a poem or joke and send it in an e-mail to Kai.", null)]
[InlineData("", "Write a poem or joke and send it in an e-mail to Kai.", "")]
[InlineData("Hello World!", "Write a poem or joke and send it in an e-mail to Kai.", "some_email@email.com")]
public async Task CanExecuteRunPlanSimpleManualStateAsync(string input, string goal, string email)
@@ -180,7 +179,6 @@ public sealed class PlanTests : IDisposable
}
[Theory]
- [InlineData(null, "Write a poem or joke and send it in an e-mail to Kai.", null)]
[InlineData("", "Write a poem or joke and send it in an e-mail to Kai.", "")]
[InlineData("Hello World!", "Write a poem or joke and send it in an e-mail to Kai.", "some_email@email.com")]
public async Task CanExecuteRunPlanSimpleManualStateNoVariableAsync(string input, string goal, string email)
@@ -214,14 +212,12 @@ public sealed class PlanTests : IDisposable
}
[Theory]
- [InlineData(null, "Write a poem or joke and send it in an e-mail to Kai.", null)]
[InlineData("", "Write a poem or joke and send it in an e-mail to Kai.", "")]
[InlineData("Hello World!", "Write a poem or joke and send it in an e-mail to Kai.", "some_email@email.com")]
public async Task CanExecuteRunPlanManualStateAsync(string input, string goal, string email)
{
// Arrange
IKernel target = this.InitializeKernel();
-
var emailSkill = target.ImportSkill(new EmailSkillFake());
// Create the input mapping from parent (plan) plan state to child plan (sendEmailPlan) state.
diff --git a/dotnet/src/IntegrationTests/RedirectOutput.cs b/dotnet/src/IntegrationTests/RedirectOutput.cs
index 5aa902e6c9..64c374c57c 100644
--- a/dotnet/src/IntegrationTests/RedirectOutput.cs
+++ b/dotnet/src/IntegrationTests/RedirectOutput.cs
@@ -27,9 +27,9 @@ public class RedirectOutput : TextWriter, ILogger
this._logs.AppendLine(value);
}
- public IDisposable? BeginScope(TState state) where TState : notnull
+ public IDisposable BeginScope(TState state)
{
- return null;
+ return null!;
}
public bool IsEnabled(LogLevel logLevel)
diff --git a/dotnet/src/IntegrationTests/Security/TrustServiceTests.cs b/dotnet/src/IntegrationTests/Security/TrustServiceTests.cs
deleted file mode 100644
index 12a9bbe92d..0000000000
--- a/dotnet/src/IntegrationTests/Security/TrustServiceTests.cs
+++ /dev/null
@@ -1,420 +0,0 @@
-īģŋ// Copyright (c) Microsoft. All rights reserved.
-
-using System.ComponentModel;
-using Microsoft.Extensions.Configuration;
-using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
-using Microsoft.SemanticKernel.SkillDefinition;
-using SemanticKernel.IntegrationTests.TestSettings;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace SemanticKernel.IntegrationTests.Security;
-
-public sealed class TrustServiceTests
-{
- public TrustServiceTests(ITestOutputHelper output)
- {
- this._configuration = new ConfigurationBuilder()
- .AddJsonFile(path: "testsettings.json", optional: false, reloadOnChange: true)
- .AddJsonFile(path: "testsettings.development.json", optional: true, reloadOnChange: true)
- .AddEnvironmentVariables()
- .AddUserSecrets()
- .Build();
- }
-
- [Fact]
- public async void FlowWithUntrustedContentAndNotSensitiveSemanticFunctionShouldSucceed()
- {
- // Arrange
- var valueToEcho = "Hello AI :)";
- var kernel = this.InitializeKernel();
-
- var defaultUntrustedEchoFunction = this.CreateSemanticEchoFunction(
- kernel,
- functionName: "DefaultUntrustedEchoFunction",
- isSensitive: false,
- trustService: TrustService.DefaultUntrusted
- );
- var defaultTrustedEchoFunction = this.CreateSemanticEchoFunction(
- kernel,
- functionName: "DefaultTrustedEchoFunction",
- isSensitive: false,
- trustService: TrustService.DefaultTrusted
- );
-
- // Act
- var result = await kernel.RunAsync(valueToEcho, defaultUntrustedEchoFunction, defaultTrustedEchoFunction);
-
- // Assert
- Assert.Equal(valueToEcho, result.Result);
- Assert.False(result.IsTrusted);
- }
-
- [Fact]
- public async void FlowWithUntrustedContentAndSensitiveSemanticFunctionShouldThrow()
- {
- // Arrange
- var valueToEcho = "Hello AI :)";
-
- var kernel = this.InitializeKernel();
-
- var notSensitiveEchoFunction = this.CreateSemanticEchoFunction(
- kernel,
- functionName: "NotSensitiveEchoFunction",
- isSensitive: false,
- trustService: TrustService.DefaultUntrusted
- );
- var sensitiveEchoFunction = this.CreateSemanticEchoFunction(
- kernel,
- functionName: "SensitiveEchoFunction",
- isSensitive: true,
- trustService: TrustService.DefaultTrusted
- );
-
- // Act
- var result = await kernel.RunAsync(valueToEcho, notSensitiveEchoFunction, sensitiveEchoFunction);
-
- // Assert
- this.AssertResultHasThrown(result);
- }
-
- [Fact]
- public async void FlowWithUntrustedContentAndNotSensitiveNativeFunctionShouldSucceed()
- {
- // Arrange
- var valueToEcho = "Hello AI :)";
- var kernel = this.InitializeKernel();
-
- var defaultUntrustedEchoFunction = this.CreateNativeEchoFunction(
- kernel,
- isSensitive: false,
- trustService: TrustService.DefaultUntrusted
- );
- var defaultTrustedEchoFunction = this.CreateNativeEchoFunction(
- kernel,
- isSensitive: false,
- trustService: TrustService.DefaultTrusted
- );
-
- // Act
- var result = await kernel.RunAsync(valueToEcho, defaultUntrustedEchoFunction, defaultTrustedEchoFunction);
-
- // Assert
- Assert.Equal(valueToEcho, result.Result);
- Assert.False(result.IsTrusted);
- }
-
- [Fact]
- public async void FlowWithUntrustedContentAndSensitiveNativeFunctionShouldThrow()
- {
- // Arrange
- var valueToEcho = "Hello AI :)";
- var kernel = this.InitializeKernel();
-
- var notSensitiveEchoFunction = this.CreateNativeEchoFunction(
- kernel,
- isSensitive: false,
- trustService: TrustService.DefaultUntrusted
- );
- var sensitiveEchoFunction = this.CreateNativeEchoFunction(
- kernel,
- isSensitive: true,
- trustService: TrustService.DefaultTrusted
- );
-
- // Act
- var result = await kernel.RunAsync(valueToEcho, notSensitiveEchoFunction, sensitiveEchoFunction);
-
- // Assert
- this.AssertResultHasThrown(result);
- }
-
- [Fact]
- public async void UntrustedVariableAndNotSensitiveSemanticFunctionShouldSucceed()
- {
- // Arrange
- var valueToEcho = "Hello AI, ";
- var extraVar = "welcome!";
- var variables = new ContextVariables(valueToEcho);
- var kernel = this.InitializeKernel();
-
- var notSensitiveEchoFunction = this.CreateSemanticEchoFunction(
- kernel,
- functionName: "NotSensitiveEchoFunction",
- isSensitive: false,
- trustService: null // Use defaults
- );
-
- // Make this untrusted
- variables.Set(TrustServiceTests.ExtraVarName, TrustAwareString.CreateUntrusted(extraVar));
-
- // Act
- var result = await kernel.RunAsync(variables, notSensitiveEchoFunction);
-
- // Assert
- Assert.Equal(valueToEcho + extraVar, result.Result);
- Assert.False(result.IsTrusted);
- }
-
- [Fact]
- public async void UntrustedVariableAndSensitiveSemanticFunctionShouldThrow()
- {
- // Arrange
- var valueToEcho = "Hello AI, ";
- var extraVar = "welcome!";
- var variables = new ContextVariables(valueToEcho);
- var kernel = this.InitializeKernel();
-
- var sensitiveEchoFunction = this.CreateSemanticEchoFunction(
- kernel,
- functionName: "SensitiveEchoFunction",
- isSensitive: true,
- trustService: null // Use defaults
- );
-
- // Make this untrusted
- variables.Set(TrustServiceTests.ExtraVarName, TrustAwareString.CreateUntrusted(extraVar));
-
- // Act
- var result = await kernel.RunAsync(variables, sensitiveEchoFunction);
-
- // Assert
- this.AssertResultHasThrown(result);
- }
-
- [Fact]
- public async void UntrustedVariableAndNotSensitiveNativeFunctionShouldSucceed()
- {
- // Arrange
- var valueToEcho = "Hello AI, ";
- var extraVar = "welcome!";
- var variables = new ContextVariables(valueToEcho);
- var kernel = this.InitializeKernel();
-
- var notSensitiveEchoFunction = this.CreateNativeEchoFunction(
- kernel,
- isSensitive: false,
- trustService: null // Use defaults
- );
-
- // Make this untrusted
- variables.Set(TrustServiceTests.ExtraVarName, TrustAwareString.CreateUntrusted(extraVar));
-
- // Act
- var result = await kernel.RunAsync(variables, notSensitiveEchoFunction);
-
- // Assert
- Assert.Equal(valueToEcho + extraVar, result.Result);
- Assert.False(result.IsTrusted);
- }
-
- [Fact]
- public async void UntrustedVariableAndSensitiveNativeFunctionShouldThrow()
- {
- // Arrange
- var valueToEcho = "Hello AI, ";
- var extraVar = "welcome!";
- var variables = new ContextVariables(valueToEcho);
- var kernel = this.InitializeKernel();
-
- var sensitiveEchoFunction = this.CreateNativeEchoFunction(
- kernel,
- isSensitive: true,
- trustService: null // Use defaults
- );
-
- // Make this untrusted
- variables.Set(TrustServiceTests.ExtraVarName, TrustAwareString.CreateUntrusted(extraVar));
-
- // Act
- var result = await kernel.RunAsync(variables, sensitiveEchoFunction);
-
- // Assert
- this.AssertResultHasThrown(result);
- }
-
- [Fact]
- public async void NotSensitiveFunctionWithTrustedFunctionCallShouldSucceed()
- {
- // Arrange
- var valueToEcho = "Hello AI :)";
- var kernel = this.InitializeKernel();
-
- this.CreateNativeEchoFunction(
- kernel,
- isSensitive: true,
- trustService: TrustService.DefaultTrusted
- );
-
- var sensitiveEchoWithFunctionCallFunction = this.CreateSemanticEchoWithFunctionCallFunction(
- kernel,
- functionName: "SensitiveEchoWithFunctionCallFunction",
- isSensitive: true,
- trustService: null // Use defaults
- );
-
- // Act
- var result = await kernel.RunAsync(valueToEcho, sensitiveEchoWithFunctionCallFunction);
-
- // Assert
- Assert.Equal(valueToEcho, result.Result);
- Assert.True(result.IsTrusted);
- }
-
- [Fact]
- public async void SensitiveFunctionWithUntrustedFunctionCallShouldThrow()
- {
- // Arrange
- var valueToEcho = "Hello AI :)";
- var kernel = this.InitializeKernel();
-
- this.CreateNativeEchoFunction(
- kernel,
- isSensitive: true,
- trustService: TrustService.DefaultUntrusted // Forces output to be untrusted
- );
-
- var sensitiveEchoWithFunctionCallFunction = this.CreateSemanticEchoWithFunctionCallFunction(
- kernel,
- functionName: "SensitiveEchoWithFunctionCallFunction",
- isSensitive: true,
- trustService: null // Use defaults
- );
-
- // Act
- var result = await kernel.RunAsync(valueToEcho, sensitiveEchoWithFunctionCallFunction);
-
- // Assert
- this.AssertResultHasThrown(result);
- }
-
- private IKernel InitializeKernel()
- {
- AzureOpenAIConfiguration? azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get();
- Assert.NotNull(azureOpenAIConfiguration);
-
- var builder = Kernel.Builder
- .WithAzureTextCompletionService(
- deploymentName: azureOpenAIConfiguration.DeploymentName,
- endpoint: azureOpenAIConfiguration.Endpoint,
- apiKey: azureOpenAIConfiguration.ApiKey,
- serviceId: azureOpenAIConfiguration.ServiceId,
- setAsDefault: true
- );
-
- return builder.Build();
- }
-
- private ISKFunction CreateSemanticEchoFunction(
- IKernel kernel,
- string functionName,
- bool isSensitive,
- ITrustService? trustService)
- {
- string promptTemplate = TrustServiceTests.SemanticEchoPrompt.Replace(
- TrustServiceTests.WhatToEcho,
- $"{{{{$input}}}}{{{{${TrustServiceTests.ExtraVarName}}}}}",
- System.StringComparison.Ordinal
- );
-
- return kernel.CreateSemanticFunction(
- promptTemplate: promptTemplate,
- functionName: functionName,
- skillName: "CustomSkill",
- isSensitive: isSensitive,
- trustService: trustService
- );
- }
-
- private ISKFunction CreateSemanticEchoWithFunctionCallFunction(
- IKernel kernel,
- string functionName,
- bool isSensitive,
- ITrustService? trustService)
- {
- string promptTemplate = TrustServiceTests.SemanticEchoPrompt.Replace(
- TrustServiceTests.WhatToEcho,
- $"{{{{{nameof(EchoSkill)}.{nameof(EchoSkill.NotSensitiveEcho)} $input}}}}",
- System.StringComparison.Ordinal
- );
-
- return kernel.CreateSemanticFunction(
- promptTemplate: promptTemplate,
- functionName: functionName,
- skillName: "CustomSkill",
- isSensitive: isSensitive,
- trustService: trustService
- );
- }
-
- private ISKFunction CreateNativeEchoFunction(
- IKernel kernel,
- bool isSensitive,
- ITrustService? trustService)
- {
- var skill = kernel.ImportSkill(
- new EchoSkill(),
- nameof(EchoSkill),
- trustService: trustService
- );
-
- return isSensitive ?
- skill[nameof(EchoSkill.SensitiveEcho)] :
- skill[nameof(EchoSkill.NotSensitiveEcho)];
- }
-
- private void AssertResultHasThrown(SKContext result)
- {
- Assert.True(result.ErrorOccurred);
- Assert.IsType(result.LastException);
- Assert.Equal(
- UntrustedContentException.ErrorCodes.SensitiveFunctionWithUntrustedContent,
- ((UntrustedContentException)result.LastException).ErrorCode
- );
- }
-
- private readonly IConfigurationRoot _configuration;
-
- private sealed class EchoSkill
- {
- [SKFunction(isSensitive: false), Description("Echoes a given text")]
- public string NotSensitiveEcho(string input, string? extraVar = null) => input + extraVar;
-
- [SKFunction(isSensitive: true), Description("Echoes a given text")]
- public string SensitiveEcho(string input, string? extraVar = null) => input + extraVar;
- }
-
- private const string WhatToEcho = "[WHAT_TO_ECHO]";
-
- private const string ExtraVarName = "extraVar";
-
- private const string SemanticEchoPrompt = @$"Your goal is just to echo whatever input is given to you.
-
-[EXAMPLES]
-INPUT
--------------------
-Hello, how are you?
-
-RESULT
--------------------
-Hello, how are you?
-
-INPUT
--------------------
-Hi!
-
-RESULT
--------------------
-Hi!
-[END EXAMPLES]
-
-INPUT
--------------------
-{TrustServiceTests.WhatToEcho}
-
-RESULT
--------------------
-";
-}
diff --git a/dotnet/src/IntegrationTests/XunitLogger.cs b/dotnet/src/IntegrationTests/XunitLogger.cs
index a61dffec37..9118d31676 100644
--- a/dotnet/src/IntegrationTests/XunitLogger.cs
+++ b/dotnet/src/IntegrationTests/XunitLogger.cs
@@ -28,7 +28,7 @@ internal sealed class XunitLogger : ILogger, IDisposable
public bool IsEnabled(LogLevel logLevel) => true;
///
- public IDisposable BeginScope(TState state) where TState : notnull
+ public IDisposable BeginScope(TState state)
=> this;
///
diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatRequestSettings.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatRequestSettings.cs
index 0d2ed0305b..c74373e2f3 100644
--- a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatRequestSettings.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatRequestSettings.cs
@@ -52,7 +52,7 @@ public class ChatRequestSettings
///
/// The maximum number of tokens to generate in the completion.
///
- public int MaxTokens { get; set; } = 256;
+ public int? MaxTokens { get; set; }
///
/// Modify the likelihood of specified tokens appearing in the completion.
diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/CompleteRequestSettings.cs b/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/CompleteRequestSettings.cs
index da383f131a..5999e25cfc 100644
--- a/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/CompleteRequestSettings.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/CompleteRequestSettings.cs
@@ -40,7 +40,7 @@ public class CompleteRequestSettings
///
/// The maximum number of tokens to generate in the completion.
///
- public int MaxTokens { get; set; } = 256;
+ public int? MaxTokens { get; set; }
///
/// Sequences where the completion will stop generating further tokens.
diff --git a/dotnet/src/SemanticKernel.Abstractions/IKernel.cs b/dotnet/src/SemanticKernel.Abstractions/IKernel.cs
index 2426c2b316..0df0ef7ff1 100644
--- a/dotnet/src/SemanticKernel.Abstractions/IKernel.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/IKernel.cs
@@ -6,7 +6,6 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
using Microsoft.SemanticKernel.SemanticFunctions;
using Microsoft.SemanticKernel.Services;
using Microsoft.SemanticKernel.SkillDefinition;
@@ -44,24 +43,15 @@ public interface IKernel
///
IReadOnlySkillCollection Skills { get; }
- ///
- /// Default service for trust check events in case a specific one is not provided at function creation.
- /// Functions directly created through the kernel will use this trust service if no other is provided.
- /// If null, the created functions will rely on the TrustService.DefaultTrusted implementation.
- ///
- ITrustService? TrustServiceInstance { get; }
-
///
/// Build and register a function in the internal skill collection, in a global generic skill.
///
/// Name of the semantic function. The name can contain only alphanumeric chars + underscore.
/// Function configuration, e.g. I/O params, AI settings, localization details, etc.
- /// Service used for trust checks (if null will use the default registered in the kernel).
/// A C# function wrapping AI logic, usually defined with natural language
ISKFunction RegisterSemanticFunction(
string functionName,
- SemanticFunctionConfig functionConfig,
- ITrustService? trustService = null);
+ SemanticFunctionConfig functionConfig);
///
/// Build and register a function in the internal skill collection.
@@ -69,13 +59,11 @@ public interface IKernel
/// Name of the skill containing the function. The name can contain only alphanumeric chars + underscore.
/// Name of the semantic function. The name can contain only alphanumeric chars + underscore.
/// Function configuration, e.g. I/O params, AI settings, localization details, etc.
- /// Service used for trust checks (if null will use the default registered in the kernel).
/// A C# function wrapping AI logic, usually defined with natural language
ISKFunction RegisterSemanticFunction(
string skillName,
string functionName,
- SemanticFunctionConfig functionConfig,
- ITrustService? trustService = null);
+ SemanticFunctionConfig functionConfig);
///
/// Registers a custom function in the internal skill collection.
@@ -90,9 +78,8 @@ public interface IKernel
///
/// Instance of a class containing functions
/// Name of the skill for skill collection and prompt templates. If the value is empty functions are registered in the global namespace.
- /// Service used for trust checks (if null will use the default registered in the kernel).
/// A list of all the semantic functions found in the directory, indexed by function name.
- IDictionary ImportSkill(object skillInstance, string? skillName = null, ITrustService? trustService = null);
+ IDictionary ImportSkill(object skillInstance, string? skillName = null);
///
/// Set the semantic memory to use
diff --git a/dotnet/src/SemanticKernel.Abstractions/Orchestration/ContextVariables.cs b/dotnet/src/SemanticKernel.Abstractions/Orchestration/ContextVariables.cs
index c78ede899e..4ed2404ba8 100644
--- a/dotnet/src/SemanticKernel.Abstractions/Orchestration/ContextVariables.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/Orchestration/ContextVariables.cs
@@ -4,11 +4,9 @@ using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.SemanticKernel.Diagnostics;
-using Microsoft.SemanticKernel.Security;
#pragma warning disable CA1710 // ContextVariables doesn't end in Dictionary or Collection
#pragma warning disable CA1725, RCS1168 // Uses "name" instead of "key" for some public APIs
@@ -23,24 +21,17 @@ namespace Microsoft.SemanticKernel.Orchestration;
///
[DebuggerDisplay("{DebuggerDisplay,nq}")]
[DebuggerTypeProxy(typeof(ContextVariables.TypeProxy))]
-public sealed class ContextVariables : IDictionary
+public sealed class ContextVariables : IDictionary
{
///
/// Constructor for context variables.
///
- /// Optional value for the main variable of the context including trust information.
- public ContextVariables(TrustAwareString? trustAwareContent = null)
+ /// Optional value for the main variable of the context including trust information.
+ public ContextVariables(string? value = null)
{
- this._variables[MainKey] = trustAwareContent ?? TrustAwareString.Empty;
+ this._variables[MainKey] = value ?? string.Empty;
}
- ///
- /// Constructor for context variables.
- /// By default the content will be trusted.
- ///
- /// Optional value for the main variable of the context.
- public ContextVariables(string? content) : this(TrustAwareString.CreateTrusted(content)) { }
-
///
/// Create a copy of the current instance with a copy of the internal data
///
@@ -48,7 +39,7 @@ public sealed class ContextVariables : IDictionary
public ContextVariables Clone()
{
var clone = new ContextVariables();
- foreach (KeyValuePair x in this._variables)
+ foreach (KeyValuePair x in this._variables)
{
clone.Set(x.Key, x.Value);
}
@@ -58,45 +49,21 @@ public sealed class ContextVariables : IDictionary
/// Gets the main input string.
/// If the main input string was removed from the collection, an empty string will be returned.
- public TrustAwareString Input => this._variables.TryGetValue(MainKey, out TrustAwareString? value) ? value : TrustAwareString.Empty;
+ public string Input => this._variables.TryGetValue(MainKey, out string? value) ? value : string.Empty;
///
/// Updates the main input text with the new value after a function is complete.
/// The string includes trust information and will overwrite the trust state of the input.
///
- /// The new input value, for the next function in the pipeline, or as a result for the user
+ /// The new input value, for the next function in the pipeline, or as a result for the user
/// if the pipeline reached the end.
/// The current instance
- public ContextVariables Update(TrustAwareString? trustAwareContent)
+ public ContextVariables Update(string? value)
{
- this._variables[MainKey] = trustAwareContent ?? TrustAwareString.Empty;
+ this._variables[MainKey] = value ?? string.Empty;
return this;
}
- ///
- /// Updates the main input text with the new value after a function is complete.
- /// By default the content will be trusted.
- ///
- /// The new input value, for the next function in the pipeline, or as a result for the user
- /// if the pipeline reached the end.
- /// The current instance
- public ContextVariables Update(string? content)
- {
- return this.Update(TrustAwareString.CreateTrusted(content));
- }
-
- ///
- /// Updates the main input text with the new value after a function is complete.
- /// This will keep the trust state of the current input set.
- ///
- /// The new input value, for the next function in the pipeline, or as a result for the user
- /// if the pipeline reached the end.
- /// The current instance
- public ContextVariables UpdateKeepingTrustState(string? content)
- {
- return this.Update(new TrustAwareString(content, this.Input.IsTrusted));
- }
-
///
/// Updates all the local data with new data, merging the two datasets.
/// Do not discard old data
@@ -111,7 +78,7 @@ public sealed class ContextVariables : IDictionary
// If requested, discard old data and keep only the new one.
if (!merge) { this._variables.Clear(); }
- foreach (KeyValuePair varData in newData._variables)
+ foreach (KeyValuePair varData in newData._variables)
{
this._variables[varData.Key] = varData.Value;
}
@@ -127,13 +94,13 @@ public sealed class ContextVariables : IDictionary
/// The string value includes trust information and will overwrite the trust information already stored for the variable.
///
/// Variable name
- /// Value to store. If the value is NULL the variable is deleted.
- public void Set(string name, TrustAwareString? trustAwareValue)
+ /// Value to store. If the value is NULL the variable is deleted.
+ public void Set(string name, string? value)
{
Verify.NotNullOrWhiteSpace(name);
- if (trustAwareValue != null)
+ if (value != null)
{
- this._variables[name] = trustAwareValue;
+ this._variables[name] = value;
}
else
{
@@ -141,68 +108,6 @@ public sealed class ContextVariables : IDictionary
}
}
- ///
- /// This method allows to store additional data in the context variables, e.g. variables needed by functions in the
- /// pipeline. These "variables" are visible also to semantic functions using the "{{varName}}" syntax, allowing
- /// to inject more information into prompt templates.
- /// By default the variables' value will be trusted.
- ///
- /// Variable name
- /// Value to store
- public void Set(string name, string value)
- {
- this.Set(name, TrustAwareString.CreateTrusted(value));
- }
-
- ///
- /// Fetch a variable value and if its content is trusted from the context variables.
- ///
- /// Variable name
- /// Variable value as a string with trust information
- /// Whether the value exists in the context variables
- [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Use the TryGetValue method or indexer instead.")]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool Get(string name, out TrustAwareString trustAwareValue)
- {
- if (this._variables.TryGetValue(name, out TrustAwareString result))
- {
- trustAwareValue = result;
- return true;
- }
-
- trustAwareValue = TrustAwareString.Empty;
- return false;
- }
-
- ///
- /// Fetch a variable value from the context variables.
- ///
- /// Variable name
- /// Value
- /// Whether the value exists in the context variables
- [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Use the TryGetValue method or indexer instead.")]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool Get(string name, out string value)
- {
- var exists = this.Get(name, out TrustAwareString trustAwareValue);
-
- value = trustAwareValue.Value;
-
- return exists;
- }
-
- ///
- /// Gets the variable value associated with the specified name.
- ///
- /// The name of the variable to get.
- ///
- /// When this method returns, contains the variable value associated with the specified name, if the variable is found;
- /// otherwise, null.
- ///
- /// true if the contains a variable with the specified name; otherwise, false.
- public bool TryGetValue(string name, [NotNullWhen(true)] out TrustAwareString? value) =>
- this._variables.TryGetValue(name, out value);
-
///
/// Gets the variable value associated with the specified name.
///
@@ -214,9 +119,8 @@ public sealed class ContextVariables : IDictionary
/// true if the contains a variable with the specified name; otherwise, false.
public bool TryGetValue(string name, [NotNullWhen(true)] out string? value)
{
- if (this._variables.TryGetValue(name, out TrustAwareString? trustAwareValue))
+ if (this._variables.TryGetValue(name, out value))
{
- value = trustAwareValue.Value;
return true;
}
@@ -231,14 +135,10 @@ public sealed class ContextVariables : IDictionary
/// The value of the variable.
public string this[string name]
{
- get => this._variables[name].Value;
+ get => this._variables[name];
set
{
- // This will be trusted by default for now.
- // TODO: we could plan to replace string usages in the kernel
- // with TrustAwareString, so here "value" could directly be a trust aware string
- // including trust information
- this._variables[name] = TrustAwareString.CreateTrusted(value);
+ this._variables[name] = value;
}
}
@@ -252,42 +152,6 @@ public sealed class ContextVariables : IDictionary
return this._variables.ContainsKey(name);
}
- ///
- /// True if all the stored variables have trusted content.
- ///
- ///
- public bool IsAllTrusted()
- {
- foreach (var pair in this._variables)
- {
- if (!pair.Value.IsTrusted)
- {
- return false;
- }
- }
- return true;
- }
-
- ///
- /// Make all the variables stored in the context untrusted.
- ///
- public void UntrustAll()
- {
- foreach (var item in this._variables)
- {
- // Note: we don't use an internal setter for better multi-threading
- this._variables[item.Key] = TrustAwareString.CreateUntrusted(item.Value.Value);
- }
- }
-
- ///
- /// Make the input variable untrusted.
- ///
- public void UntrustInput()
- {
- this.Update(TrustAwareString.CreateUntrusted(this.Input.Value));
- }
-
///
/// Print the processed input, aka the current data after any processing occurred.
///
@@ -298,24 +162,24 @@ public sealed class ContextVariables : IDictionary
/// Get an enumerator that iterates through the context variables.
///
/// An enumerator that iterates through the context variables
- public IEnumerator> GetEnumerator() => this._variables.GetEnumerator();
+ public IEnumerator> GetEnumerator() => this._variables.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this._variables.GetEnumerator();
- void IDictionary.Add(string key, TrustAwareString value) => ((IDictionary)this._variables).Add(key, value);
- bool IDictionary.Remove(string key) => ((IDictionary)this._variables).Remove(key);
- void ICollection>.Add(KeyValuePair item) => ((ICollection>)this._variables).Add(item);
- void ICollection>.Clear() => ((ICollection>)this._variables).Clear();
- bool ICollection>.Contains(KeyValuePair item) => ((ICollection>)this._variables).Contains(item);
- void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => ((ICollection>)this._variables).CopyTo(array, arrayIndex);
- bool ICollection>.Remove(KeyValuePair item) => ((ICollection>)this._variables).Remove(item);
- ICollection IDictionary.Keys => ((IDictionary)this._variables).Keys;
- ICollection IDictionary.Values => ((IDictionary)this._variables).Values;
- int ICollection>.Count => ((ICollection>)this._variables).Count;
- bool ICollection>.IsReadOnly => ((ICollection>)this._variables).IsReadOnly;
- TrustAwareString IDictionary.this[string key]
+ void IDictionary.Add(string key, string value) => ((IDictionary)this._variables).Add(key, value);
+ bool IDictionary.Remove(string key) => ((IDictionary)this._variables).Remove(key);
+ void ICollection>.Add(KeyValuePair item) => ((ICollection>)this._variables).Add(item);
+ void ICollection>.Clear() => ((ICollection>)this._variables).Clear();
+ bool ICollection>.Contains(KeyValuePair item) => ((ICollection>)this._variables).Contains(item);
+ void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => ((ICollection>)this._variables).CopyTo(array, arrayIndex);
+ bool ICollection>.Remove(KeyValuePair item) => ((ICollection>)this._variables).Remove(item);
+ ICollection IDictionary.Keys => ((IDictionary)this._variables).Keys;
+ ICollection IDictionary.Values => ((IDictionary)this._variables).Values;
+ int ICollection>.Count => ((ICollection>)this._variables).Count;
+ bool ICollection>.IsReadOnly => ((ICollection>)this._variables).IsReadOnly;
+ string IDictionary.this[string key]
{
- get => ((IDictionary)this._variables)[key];
- set => ((IDictionary)this._variables)[key] = value;
+ get => ((IDictionary)this._variables)[key];
+ set => ((IDictionary)this._variables)[key] = value;
}
internal const string MainKey = "INPUT";
@@ -331,7 +195,7 @@ public sealed class ContextVariables : IDictionary
///
/// Important: names are case insensitive
///
- private readonly ConcurrentDictionary _variables = new(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary _variables = new(StringComparer.OrdinalIgnoreCase);
private sealed class TypeProxy
{
@@ -340,7 +204,7 @@ public sealed class ContextVariables : IDictionary
public TypeProxy(ContextVariables variables) => this._variables = variables;
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
- public KeyValuePair[] Items => this._variables._variables.ToArray();
+ public KeyValuePair[] Items => this._variables._variables.ToArray();
}
#endregion
diff --git a/dotnet/src/SemanticKernel.Abstractions/Orchestration/SKContext.cs b/dotnet/src/SemanticKernel.Abstractions/Orchestration/SKContext.cs
index 0b6c873652..f2027dc2d8 100644
--- a/dotnet/src/SemanticKernel.Abstractions/Orchestration/SKContext.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/Orchestration/SKContext.cs
@@ -29,11 +29,6 @@ public sealed class SKContext
/// Processed input, aka result
public string Result => this.Variables.ToString();
- ///
- /// Whether all the context variables are trusted or not.
- ///
- public bool IsTrusted => this.Variables.IsAllTrusted();
-
///
/// Whether an error occurred while executing functions in the pipeline.
///
@@ -156,22 +151,6 @@ public sealed class SKContext
this._culture = CultureInfo.CurrentCulture;
}
- ///
- /// Make all the variables stored in the context untrusted.
- ///
- public void UntrustAll()
- {
- this.Variables.UntrustAll();
- }
-
- ///
- /// Make the result untrusted.
- ///
- public void UntrustResult()
- {
- this.Variables.UntrustInput();
- }
-
///
/// Print the processed input, aka the current data after any processing occurred.
/// If an error occurred, prints the last exception message instead.
diff --git a/dotnet/src/SemanticKernel.Abstractions/Security/ITrustService.cs b/dotnet/src/SemanticKernel.Abstractions/Security/ITrustService.cs
deleted file mode 100644
index 4e18e8033c..0000000000
--- a/dotnet/src/SemanticKernel.Abstractions/Security/ITrustService.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-īģŋ// Copyright (c) Microsoft. All rights reserved.
-
-using System.Threading.Tasks;
-using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.SkillDefinition;
-
-namespace Microsoft.SemanticKernel.Security;
-
-///
-/// Base interface used to handle trust events and validation. The flow in the SKFunction is:
-///
-/// - Semantic function
-/// - Call ValidateContextAsync
-/// - Render prompt using template and variables
-/// - Call ValidatePromptAsync with rendered template and context
-/// - Call completion client with the returned prompt from ValidatePromptAsync
-/// - Update result
-///
-/// - Native function
-/// - Call ValidateContextAsync
-/// - Call native function implementation
-/// - Update result
-///
-public interface ITrustService
-{
- ///
- /// Called by the SKFunction flow to validate if the current context is considered to be trusted or not:
- ///
- /// - This is called for semantic functions before rendering the prompt template.
- /// - This is called for native functions before calling the native function implementation.
- ///
- /// If the return is false, this means the result of the function will be tagged as untrusted.
- ///
- /// The implementation might depend on the application needs. A simple sample implementation
- /// could be accomplished by analyzing the variables in the context, if they are all trusted, then
- /// consider the context to be trusted.
- ///
- /// This also gives an opportunity for the context to be updated or actions to be taken if
- /// potentially untrusted content is found. For example, sanitizing an untrusted variable and turning it into trusted.
- ///
- /// Instance of the function being called
- /// The current execution context
- /// Should return whether the result of the function should be considered trusted depending on the context
- Task ValidateContextAsync(ISKFunction func, SKContext context);
-
- ///
- /// This will only be called by semantic functions. It will be called in the SKFunction flow after the prompt is
- /// rendered using the given template, and before calling the text completion with the rendered prompt.
- ///
- /// It should return the content to be used in the completion client as a TrustAwareString, which will include
- /// trust information. If the TrustAwareString returned is not trusted, this means the result of the function will be tagged as untrusted.
- ///
- /// After the template is rendered, the context might be tagged as untrusted because the template might contain function calls
- /// that turned the context into untrusted when rendered.
- ///
- /// The implementation might depend on the application needs. A simple sample implementation
- /// could be accomplished by analyzing the variables in the context and the rendered prompt, if everything is trusted, then
- /// return the prompt wrapped in a TrustAwareString and tagged as trusted.
- ///
- /// This also gives an opportunity for both the context and the prompt to be updated before calling the completion client
- /// when something untrusted is identified. For example, sanitizing an untrusted prompt and turning it into trusted.
- ///
- /// Instance of the function being called
- /// The current execution context
- /// The current rendered prompt to be used with the completion client
- /// Should return a TrustAwareString representing the final prompt to be used for the completion client.
- /// The TrustAwareString includes trust information
- Task ValidatePromptAsync(ISKFunction func, SKContext context, string prompt);
-}
diff --git a/dotnet/src/SemanticKernel.Abstractions/Security/TrustAwareString.cs b/dotnet/src/SemanticKernel.Abstractions/Security/TrustAwareString.cs
deleted file mode 100644
index ffcbbaee25..0000000000
--- a/dotnet/src/SemanticKernel.Abstractions/Security/TrustAwareString.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-īģŋ// Copyright (c) Microsoft. All rights reserved.
-
-using System;
-using System.Diagnostics;
-
-namespace Microsoft.SemanticKernel.Security;
-
-///
-/// Provides an immutable string that carries trust information.
-///
-[DebuggerDisplay("Value = {Value}, IsTrusted = {IsTrusted}")]
-public sealed class TrustAwareString : IEquatable
-{
- ///
- /// Gets a trusted empty string.
- ///
- public static TrustAwareString Empty { get; } = new(string.Empty, isTrusted: true);
-
- ///
- /// Create a new trusted string.
- ///
- /// The raw string value
- /// TrustAwareString
- public static TrustAwareString CreateTrusted(string? value) => new(value, isTrusted: true);
-
- ///
- /// Create a new untrusted string.
- ///
- /// The raw string value
- /// TrustAwareString
- public static TrustAwareString CreateUntrusted(string? value) => new(value, isTrusted: false);
-
- ///
- /// The raw string value.
- ///
- public string Value { get; }
-
- ///
- /// Whether the current value is trusted or not.
- ///
- public bool IsTrusted { get; }
-
- ///
- /// Create a new trust aware string.
- ///
- /// The raw string value
- /// Whether the raw string value is trusted or not
- public TrustAwareString(string? value, bool isTrusted = true)
- {
- this.Value = value ?? string.Empty;
- this.IsTrusted = isTrusted;
- }
-
- public override string ToString()
- {
- return this.Value;
- }
-
- public override bool Equals(object obj)
- {
- if (obj is null)
- {
- return false;
- }
- else if (obj is string str)
- {
- return this.Value == str;
- }
- else
- {
- return (obj is TrustAwareString trustAwareStr) && this.Equals(trustAwareStr);
- }
- }
-
- public bool Equals(TrustAwareString? other)
- {
- if (other is null)
- {
- return false;
- }
-
- return this.Value == other.Value && this.IsTrusted == other.IsTrusted;
- }
-
- public override int GetHashCode()
- {
- return HashCode.Combine(this.Value, this.IsTrusted);
- }
-
- public static bool operator ==(TrustAwareString? left, TrustAwareString? right)
- {
- if (left is null)
- {
- return right is null;
- }
-
- return left.Equals(right);
- }
-
- public static bool operator !=(TrustAwareString? left, TrustAwareString? right)
- {
- return !(left == right);
- }
-
- public static implicit operator string(TrustAwareString s) => s.Value;
-}
diff --git a/dotnet/src/SemanticKernel.Abstractions/Security/TrustService.cs b/dotnet/src/SemanticKernel.Abstractions/Security/TrustService.cs
deleted file mode 100644
index 391c2a8075..0000000000
--- a/dotnet/src/SemanticKernel.Abstractions/Security/TrustService.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-īģŋ// Copyright (c) Microsoft. All rights reserved.
-
-using System.Threading.Tasks;
-using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.SkillDefinition;
-
-namespace Microsoft.SemanticKernel.Security;
-
-///
-/// Default implementation of the trust service.
-///
-/// This is just a simple example implementation that will be used by default if no other is provided.
-///
-/// When set, throws an exception to stop execution when sensitive functions try to run with untrusted content.
-///
-public sealed class TrustService : ITrustService
-{
- ///
- /// Creates the default trusted implementation of the trust service.
- /// The default trusted version will use the trust information of the variables in the context to decide
- /// whether the result of the function call should be trusted or not.
- ///
- public static TrustService DefaultTrusted => new(true);
-
- ///
- /// Creates the default untrusted implementation of the trust service.
- /// The default untrusted version will always force the result of the function call to be untrusted.
- ///
- public static TrustService DefaultUntrusted => new(false);
-
- ///
- /// If set to:
- /// - false: will cause the context/prompt to always be considered untrusted, meaning the output of the function will always be considered untrusted.
- /// - true: will use the trust information of the variables in the context to decide whether the context/prompt is trusted or not
- /// (trusted only if all the variables within the context are trusted).
- ///
- private readonly bool _defaultTrusted;
-
- ///
- public Task ValidateContextAsync(ISKFunction func, SKContext context)
- {
- return Task.FromResult(this.InternalValidation(func, context));
- }
-
- ///
- public Task ValidatePromptAsync(ISKFunction func, SKContext context, string prompt)
- {
- var isTrusted = this.InternalValidation(func, context);
- return Task.FromResult(new TrustAwareString(
- // This is only a sample implementation that directly returns the prompt
- prompt,
- // The content of the prompt will not be used in this example validation
- isTrusted: isTrusted
- ));
- }
-
- private TrustService(bool defaultTrusted)
- {
- this._defaultTrusted = defaultTrusted;
- }
-
- private bool InternalValidation(ISKFunction func, SKContext context)
- {
- bool contextIsTrusted = context.IsTrusted;
- if (func.IsSensitive && !contextIsTrusted)
- {
- throw new UntrustedContentException(
- UntrustedContentException.ErrorCodes.SensitiveFunctionWithUntrustedContent,
- $"Could not run {func.SkillName}.{func.Name}, the function is sensitive and the input untrusted"
- );
- }
-
- // If defaultTrusted == false, will always return false
- return contextIsTrusted && this._defaultTrusted;
- }
-}
diff --git a/dotnet/src/SemanticKernel.Abstractions/Security/UntrustedContentException.cs b/dotnet/src/SemanticKernel.Abstractions/Security/UntrustedContentException.cs
deleted file mode 100644
index 75ba8a7c4d..0000000000
--- a/dotnet/src/SemanticKernel.Abstractions/Security/UntrustedContentException.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-īģŋ// Copyright (c) Microsoft. All rights reserved.
-
-using System;
-using Microsoft.SemanticKernel.Diagnostics;
-
-namespace Microsoft.SemanticKernel.Security;
-
-#pragma warning disable RCS1194 // Implement standard exception constructors
-
-///
-/// Untrusted content exception, used to warn about:
-/// - untrusted content in the context passed to any function
-/// - untrusted prompts when using semantic functions
-///
-public class UntrustedContentException : SKException
-{
- ///
- /// Initializes a new instance of the class with a provided error code.
- ///
- /// The error code.
- public UntrustedContentException(ErrorCodes errorCode)
- : this(errorCode, message: null, innerException: null)
- {
- }
-
- ///
- /// Initializes a new instance of the class with a provided error code and message.
- ///
- /// The error code.
- /// The exception message.
- public UntrustedContentException(ErrorCodes errorCode, string? message)
- : this(errorCode, message, innerException: null)
- {
- }
-
- ///
- /// Initializes a new instance of the class with a provided error code, message, and inner exception.
- ///
- /// The error code.
- /// A string that describes the error.
- /// The exception that is the cause of the current exception.
- public UntrustedContentException(ErrorCodes errorCode, string? message, Exception? innerException)
- : base(GetDefaultMessage(errorCode, message), innerException)
- {
- this.ErrorCode = errorCode;
- }
-
- ///
- /// Gets the error code for this exception.
- ///
- public ErrorCodes ErrorCode { get; }
-
- /// Translate the error code into a default message.
- private static string GetDefaultMessage(ErrorCodes errorCode, string? message)
- {
- string description = errorCode switch
- {
- ErrorCodes.SensitiveFunctionWithUntrustedContent => "Sensitive function with untrusted content",
- _ => $"Unknown error ({errorCode:G})",
- };
-
- return message is not null ? $"{description}: {message}" : description;
- }
-
- ///
- /// Error codes for .
- ///
- public enum ErrorCodes
- {
- ///
- /// Unknown error.
- ///
- UnknownError = -1,
-
- ///
- /// Sensitive function was called with untrusted content.
- ///
- SensitiveFunctionWithUntrustedContent,
- }
-}
diff --git a/dotnet/src/SemanticKernel.Abstractions/SemanticFunctions/PromptTemplateConfig.cs b/dotnet/src/SemanticKernel.Abstractions/SemanticFunctions/PromptTemplateConfig.cs
index 43432fed8d..1e3e34e436 100644
--- a/dotnet/src/SemanticKernel.Abstractions/SemanticFunctions/PromptTemplateConfig.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/SemanticFunctions/PromptTemplateConfig.cs
@@ -56,7 +56,7 @@ public class PromptTemplateConfig
///
[JsonPropertyName("max_tokens")]
[JsonPropertyOrder(5)]
- public int MaxTokens { get; set; } = 256;
+ public int? MaxTokens { get; set; }
///
/// Stop sequences are optional sequences that tells the AI model when to stop generating tokens.
@@ -152,15 +152,6 @@ public class PromptTemplateConfig
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public InputConfig Input { get; set; } = new();
- ///
- /// Whether the function is sensitive (default false).
- /// When a function is sensitive, the default trust service will throw an exception
- /// if the function is invoked passing in some untrusted input (or context, or prompt).
- ///
- [JsonPropertyName("is_sensitive")]
- [JsonPropertyOrder(7)]
- public bool IsSensitive { get; set; } = false;
-
///
/// Remove some default properties to reduce the JSON complexity.
///
diff --git a/dotnet/src/SemanticKernel.Abstractions/SkillDefinition/ISKFunction.cs b/dotnet/src/SemanticKernel.Abstractions/SkillDefinition/ISKFunction.cs
index 0b1307631c..aebaaa6bc0 100644
--- a/dotnet/src/SemanticKernel.Abstractions/SkillDefinition/ISKFunction.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/SkillDefinition/ISKFunction.cs
@@ -7,7 +7,6 @@ using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
namespace Microsoft.SemanticKernel.SkillDefinition;
@@ -38,19 +37,6 @@ public interface ISKFunction
///
bool IsSemantic { get; }
- ///
- /// Whether the function is set to be sensitive (default false).
- /// When a function is sensitive, the default trust service will throw an exception
- /// if the function is invoked passing in some untrusted input (or context, or prompt).
- ///
- bool IsSensitive { get; }
-
- ///
- /// Service used for trust check events.
- /// This can be provided at function creation, if not, the TrustService.DefaultTrusted implementation will be used.
- ///
- ITrustService TrustServiceInstance { get; }
-
///
/// AI service settings
///
diff --git a/dotnet/src/SemanticKernel.Abstractions/SkillDefinition/SKFunctionAttribute.cs b/dotnet/src/SemanticKernel.Abstractions/SkillDefinition/SKFunctionAttribute.cs
index 15eacc96d5..c2bfc0f0cd 100644
--- a/dotnet/src/SemanticKernel.Abstractions/SkillDefinition/SKFunctionAttribute.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/SkillDefinition/SKFunctionAttribute.cs
@@ -52,27 +52,19 @@ namespace Microsoft.SemanticKernel.SkillDefinition;
public sealed class SKFunctionAttribute : Attribute
{
/// Initializes the attribute.
- /// Whether the function is set to be sensitive (default false).
- public SKFunctionAttribute(bool isSensitive = false) => this.IsSensitive = isSensitive;
-
- ///
- /// Whether the function is set to be sensitive (default false).
- /// When a function is sensitive, the default trust service will throw an exception
- /// if the function is invoked passing in some untrusted input (or context, or prompt).
- ///
- public bool IsSensitive { get; }
+ public SKFunctionAttribute()
+ {
+ }
///
/// Initializes the attribute with the specified description.
///
/// Description of the function to be used by a planner to auto-discover functions.
- /// Whether the function is set to be sensitive (default false).
[Obsolete("This constructor is deprecated and will be removed in one of the next SK SDK versions.")]
[EditorBrowsable(EditorBrowsableState.Never)]
- public SKFunctionAttribute(string description, bool isSensitive = false)
+ public SKFunctionAttribute(string description)
{
this.Description = description;
- this.IsSensitive = isSensitive;
}
[Obsolete("This property is deprecated and will be removed in one of the next SK SDK versions.")]
diff --git a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs
index d0ca9cf43f..9877838222 100644
--- a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs
@@ -9,8 +9,6 @@ using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
-using Microsoft.SemanticKernel.SemanticFunctions;
using Microsoft.SemanticKernel.SkillDefinition;
using Moq;
using Xunit;
@@ -50,31 +48,6 @@ public class KernelTests
Assert.Equal(3, data.NativeFunctions["mySk"].Count);
}
- [Fact]
- [System.Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions.")]
- public async Task ItProvidesAccessToFunctionsViaSKContextObsoleteAsync()
- {
- // Arrange
- var kernel = KernelBuilder.Create();
- var factory = new Mock>();
- kernel.Config.AddTextCompletionService(factory.Object);
-
- var nativeSkill = new MySkill();
- kernel.CreateSemanticFunction("Tell me a joke", functionName: "joker", skillName: "jk", description: "Nice fun");
- var skill = kernel.ImportSkill(nativeSkill, "mySk");
-
- // Act
- SKContext result = await kernel.RunAsync(skill["ReadSkillCollectionAsync"]);
-
- // Assert - 3 functions, var name is not case sensitive
- Assert.Equal("Nice fun", result["jk.joker"]);
- Assert.Equal("Nice fun", result["JK.JOKER"]);
- Assert.Equal("Just say hello", result["mySk.sayhello"]);
- Assert.Equal("Just say hello", result["mySk.SayHello"]);
- Assert.Equal("Export info.", result["mySk.ReadSkillCollectionAsync"]);
- Assert.Equal("Export info.", result["mysk.readskillcollectionasync"]);
- }
-
[Fact]
public void ItProvidesAccessToFunctionsViaSkillCollection()
{
@@ -208,109 +181,6 @@ public class KernelTests
kernel.ImportSkill(new MySkill());
}
- [Fact]
- public void ItConfiguresCustomTrustServiceInFunctionsBySettingKernelTrustService()
- {
- // Arrange
- ITrustService trustService = new CustomTrustService();
- var factory = new Mock>();
- var kernel = Kernel.Builder
- .WithAIService("x", factory.Object)
- .WithTrustService(trustService).Build();
- var promptTemplateConfig = new PromptTemplateConfig() { IsSensitive = true };
- var promptTemplate = new PromptTemplate("Tell me a joke", promptTemplateConfig, kernel);
- var semanticFunctionConfig = new SemanticFunctionConfig(promptTemplateConfig, promptTemplate);
-
- // Act
- var nativeSkill = kernel.ImportSkill(new MySkill(), "mySk");
- var semanticSkill0 = kernel.RegisterSemanticFunction(functionName: "joker0", semanticFunctionConfig);
- var semanticSkill1 = kernel.RegisterSemanticFunction(skillName: "jk", functionName: "joker1", semanticFunctionConfig);
- var semanticSkill2 = kernel.CreateSemanticFunction("Tell me a joke", functionName: "joker2", skillName: "jk", description: "Nice fun");
- var sayHelloFunc = (SKFunction)kernel.Skills.GetFunction("mySk", "SayHello");
- var jokerFunc0 = (SKFunction)kernel.Skills.GetFunction("joker0");
- var jokerFunc1 = (SKFunction)kernel.Skills.GetFunction("jk", "joker1");
- var jokerFunc2 = (SKFunction)kernel.Skills.GetFunction("jk", "joker2");
-
- // Assert
- Assert.NotNull(kernel.TrustServiceInstance);
- Assert.NotNull(sayHelloFunc.TrustServiceInstance);
- Assert.NotNull(jokerFunc0.TrustServiceInstance);
- Assert.NotNull(jokerFunc1.TrustServiceInstance);
- Assert.NotNull(jokerFunc1.TrustServiceInstance);
- Assert.Equal(trustService, kernel.TrustServiceInstance);
- Assert.Equal(trustService, sayHelloFunc.TrustServiceInstance);
- Assert.Equal(trustService, jokerFunc0.TrustServiceInstance);
- Assert.Equal(trustService, jokerFunc1.TrustServiceInstance);
- Assert.Equal(trustService, jokerFunc2.TrustServiceInstance);
- }
-
- [Fact]
- public void ItConfiguresCustomTrustServiceInFunctionsByFunctionCreationParameter()
- {
- // Arrange
- ITrustService trustService = new CustomTrustService();
- var factory = new Mock>();
- var kernel = Kernel.Builder
- .WithAIService("x", factory.Object).Build();
- var promptTemplateConfig = new PromptTemplateConfig { IsSensitive = true };
- var promptTemplate = new PromptTemplate("Tell me a joke", promptTemplateConfig, kernel);
- var semanticFunctionConfig = new SemanticFunctionConfig(promptTemplateConfig, promptTemplate);
-
- // Act
- var nativeSkill = kernel.ImportSkill(new MySkill(), "mySk", trustService: trustService);
- var semanticSkill0 = kernel.RegisterSemanticFunction(functionName: "joker0", semanticFunctionConfig, trustService: trustService);
- var semanticSkill1 = kernel.RegisterSemanticFunction(skillName: "jk", functionName: "joker1", semanticFunctionConfig, trustService: trustService);
- var semanticSkill2 = kernel.CreateSemanticFunction("Tell me a joke", functionName: "joker2", skillName: "jk", description: "Nice fun", trustService: trustService);
- var sayHelloFunc = (SKFunction)kernel.Skills.GetFunction("mySk", "SayHello");
- var jokerFunc0 = (SKFunction)kernel.Skills.GetFunction("joker0");
- var jokerFunc1 = (SKFunction)kernel.Skills.GetFunction("jk", "joker1");
- var jokerFunc2 = (SKFunction)kernel.Skills.GetFunction("jk", "joker2");
-
- // Assert
- Assert.Null(kernel.TrustServiceInstance);
- Assert.NotNull(sayHelloFunc.TrustServiceInstance);
- Assert.NotNull(jokerFunc0.TrustServiceInstance);
- Assert.NotNull(jokerFunc1.TrustServiceInstance);
- Assert.NotNull(jokerFunc1.TrustServiceInstance);
- Assert.Equal(trustService, sayHelloFunc.TrustServiceInstance);
- Assert.Equal(trustService, jokerFunc0.TrustServiceInstance);
- Assert.Equal(trustService, jokerFunc1.TrustServiceInstance);
- Assert.Equal(trustService, jokerFunc2.TrustServiceInstance);
- }
-
- [Fact]
- public void ItUsesDefaultTrustServiceInFunctionsIfNoneIsProvided()
- {
- // Arrange
- var factory = new Mock>();
- var kernel = Kernel.Builder
- .WithAIService("x", factory.Object).Build();
- var promptTemplateConfig = new PromptTemplateConfig { IsSensitive = true };
- var promptTemplate = new PromptTemplate("Tell me a joke", promptTemplateConfig, kernel);
- var semanticFunctionConfig = new SemanticFunctionConfig(promptTemplateConfig, promptTemplate);
-
- // Act
- var nativeSkill = kernel.ImportSkill(new MySkill(), "mySk");
- var semanticSkill0 = kernel.RegisterSemanticFunction(functionName: "joker0", semanticFunctionConfig);
- var semanticSkill1 = kernel.RegisterSemanticFunction(skillName: "jk", functionName: "joker1", semanticFunctionConfig);
- var semanticSkill2 = kernel.CreateSemanticFunction("Tell me a joke", functionName: "joker2", skillName: "jk", description: "Nice fun");
- var sayHelloFunc = (SKFunction)kernel.Skills.GetFunction("mySk", "SayHello");
- var jokerFunc0 = (SKFunction)kernel.Skills.GetFunction("joker0");
- var jokerFunc1 = (SKFunction)kernel.Skills.GetFunction("jk", "joker1");
- var jokerFunc2 = (SKFunction)kernel.Skills.GetFunction("jk", "joker2");
-
- // Assert
- Assert.Null(kernel.TrustServiceInstance);
- Assert.NotNull(sayHelloFunc.TrustServiceInstance);
- Assert.NotNull(jokerFunc0.TrustServiceInstance);
- Assert.NotNull(jokerFunc1.TrustServiceInstance);
- Assert.NotNull(jokerFunc1.TrustServiceInstance);
- Assert.IsType(sayHelloFunc.TrustServiceInstance);
- Assert.IsType(jokerFunc0.TrustServiceInstance);
- Assert.IsType(jokerFunc1.TrustServiceInstance);
- Assert.IsType(jokerFunc2.TrustServiceInstance);
- }
-
public class MySkill
{
[SKFunction, Description("Return any value.")]
@@ -356,17 +226,4 @@ public class KernelTests
return context;
}
}
-
- private sealed class CustomTrustService : ITrustService
- {
- public Task ValidateContextAsync(ISKFunction func, SKContext context)
- {
- throw new NotImplementedException();
- }
-
- public Task ValidatePromptAsync(ISKFunction func, SKContext context, string prompt)
- {
- throw new NotImplementedException();
- }
- }
}
diff --git a/dotnet/src/SemanticKernel.UnitTests/Orchestration/ContextVariablesTests.cs b/dotnet/src/SemanticKernel.UnitTests/Orchestration/ContextVariablesTests.cs
index 0e7d4360a4..e624ef885a 100644
--- a/dotnet/src/SemanticKernel.UnitTests/Orchestration/ContextVariablesTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/Orchestration/ContextVariablesTests.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
using Xunit;
namespace SemanticKernel.UnitTests.Orchestration;
@@ -106,7 +105,6 @@ public class ContextVariablesTests
// Assert
Assert.True(target.TryGetValue(anyName, out string? _));
- Assert.True(target.TryGetValue(anyName, out TrustAwareString? _));
}
[Fact]
@@ -123,7 +121,6 @@ public class ContextVariablesTests
// Assert
Assert.True(target.TryGetValue(anyName, out string? _));
- Assert.True(target.TryGetValue(anyName, out TrustAwareString? _));
}
[Fact]
@@ -140,7 +137,6 @@ public class ContextVariablesTests
// Assert
Assert.True(target.TryGetValue(anyName, out string? _));
- Assert.True(target.TryGetValue(anyName, out TrustAwareString? _));
}
[Fact]
@@ -157,7 +153,6 @@ public class ContextVariablesTests
// Assert
Assert.True(target.TryGetValue(anyName, out string? _));
- Assert.True(target.TryGetValue(anyName, out TrustAwareString? _));
}
[Fact]
@@ -173,7 +168,6 @@ public class ContextVariablesTests
// Assert
Assert.True(target.TryGetValue(anyName, out string? _));
- Assert.True(target.TryGetValue(anyName, out TrustAwareString? _));
}
[Fact]
@@ -189,7 +183,6 @@ public class ContextVariablesTests
// Assert
Assert.True(target.TryGetValue(anyName, out string? _));
- Assert.True(target.TryGetValue(anyName, out TrustAwareString? _));
}
[Fact]
@@ -204,34 +197,13 @@ public class ContextVariablesTests
target.Set(anyName, anyContent);
// Assert
- AssertContextVariable(target, anyName, anyContent, true);
+ AssertContextVariable(target, anyName, anyContent);
// Act - Erase entry
target.Set(anyName, null);
// Assert - Should have been erased
Assert.False(target.TryGetValue(anyName, out string? _));
- Assert.False(target.TryGetValue(anyName, out TrustAwareString? _));
- }
-
- [Fact]
- public void GetWithTrustAwareStringSucceeds()
- {
- // Arrange
- string mainContent = Guid.NewGuid().ToString();
- string anyName = Guid.NewGuid().ToString();
- string anyContent = Guid.NewGuid().ToString();
- ContextVariables target = new(mainContent);
-
- // Act
- target.Set(anyName, TrustAwareString.CreateUntrusted(anyContent));
-
- // Assert
- Assert.True(target.TryGetValue(anyName, out TrustAwareString? trustAwareValue));
- Assert.NotNull(trustAwareValue);
- Assert.Equal(anyContent, trustAwareValue.Value);
- Assert.False(trustAwareValue.IsTrusted);
- Assert.Equal(mainContent, target.Input.Value);
}
[Fact]
@@ -244,167 +216,12 @@ public class ContextVariablesTests
ContextVariables target = new(mainContent);
// Act
- target.Set(anyName, TrustAwareString.CreateUntrusted(anyContent));
+ target.Set(anyName, anyContent);
// Assert
Assert.True(target.TryGetValue(anyName, out string? value));
Assert.Equal(anyContent, value);
- Assert.Equal(mainContent, target.Input.Value);
- }
-
- [Fact]
- public void CreateWithInputDefaultIsTrustedSucceeds()
- {
- // Arrange
- string anyContent = Guid.NewGuid().ToString();
- ContextVariables target = new(anyContent);
-
- // Assert
- Assert.Equal(anyContent, target.Input);
- AssertContextVariable(target, ContextVariables.MainKey, anyContent, true);
- }
-
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void CreateWithInputIsTrustedValueSucceeds(bool isTrusted)
- {
- // Arrange
- string anyContent = Guid.NewGuid().ToString();
- ContextVariables target = new(new TrustAwareString(anyContent, isTrusted));
-
- // Assert
- Assert.Equal(anyContent, target.Input);
- AssertContextVariable(target, ContextVariables.MainKey, anyContent, isTrusted);
- }
-
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void KeepInputContentAndUpdateIsTrustedSucceeds(bool isTrusted)
- {
- // Arrange
- string anyContent = Guid.NewGuid().ToString();
- ContextVariables target = new(new TrustAwareString(anyContent, !isTrusted));
-
- // Assert
- Assert.Equal(anyContent, target.Input);
- AssertContextVariable(target, ContextVariables.MainKey, anyContent, !isTrusted);
-
- // Act
- target.Update(new TrustAwareString(target.Input, isTrusted));
-
- // Assert
- Assert.Equal(anyContent, target.Input);
- AssertContextVariable(target, ContextVariables.MainKey, anyContent, isTrusted);
- }
-
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void UpdateInputContentAndUpdateIsTrustedSucceeds(bool isTrusted)
- {
- // Arrange
- string anyContent = Guid.NewGuid().ToString();
- string newContent = Guid.NewGuid().ToString();
- ContextVariables target = new(new TrustAwareString(anyContent, !isTrusted));
-
- // Assert
- Assert.Equal(anyContent, target.Input);
- AssertContextVariable(target, ContextVariables.MainKey, anyContent, !isTrusted);
-
- // Act
- target.Update(new TrustAwareString(newContent, isTrusted));
-
- // Assert
- Assert.Equal(newContent, target.Input);
- AssertContextVariable(target, ContextVariables.MainKey, newContent, isTrusted);
- }
-
- [Fact]
- public void UpdateWithStringDefaultsToTrustedSucceeds()
- {
- // Arrange
- string content0 = Guid.NewGuid().ToString();
- string content1 = Guid.NewGuid().ToString();
- ContextVariables trustedVar = new(TrustAwareString.CreateTrusted(Guid.NewGuid().ToString()));
- ContextVariables untrustedVar = new(TrustAwareString.CreateUntrusted(Guid.NewGuid().ToString()));
-
- // Act
- trustedVar.Update(content0);
- untrustedVar.Update(content1);
-
- // Assert
- AssertContextVariable(trustedVar, ContextVariables.MainKey, content0, true);
- AssertContextVariable(untrustedVar, ContextVariables.MainKey, content1, true);
- }
-
- [Fact]
- public void UpdateWithNullDefaultsToEmptyTrustedSucceeds()
- {
- // Arrange
- ContextVariables untrustedVar = new(TrustAwareString.CreateUntrusted(Guid.NewGuid().ToString()));
-
- // Act
- untrustedVar.Update(null);
-
- // Assert
- AssertContextVariable(untrustedVar, ContextVariables.MainKey, string.Empty, true);
- }
-
- [Fact]
- public void UpdateUntrustedSucceeds()
- {
- // Arrange
- string trustedContent = Guid.NewGuid().ToString();
- string untrustedContent = Guid.NewGuid().ToString();
- ContextVariables trustedVar = new(TrustAwareString.CreateTrusted(Guid.NewGuid().ToString()));
- ContextVariables untrustedVar = new(TrustAwareString.CreateUntrusted(Guid.NewGuid().ToString()));
-
- // Act
- trustedVar.Update(TrustAwareString.CreateUntrusted(trustedContent));
- untrustedVar.Update(TrustAwareString.CreateUntrusted(untrustedContent));
-
- // Assert
- AssertContextVariable(trustedVar, ContextVariables.MainKey, trustedContent, false);
- AssertContextVariable(untrustedVar, ContextVariables.MainKey, untrustedContent, false);
- }
-
- [Theory]
- [InlineData(false)]
- [InlineData(true)]
- public void UpdateKeepingTrustStateSucceeds(bool isTrusted)
- {
- // Arrange
- string someContent = Guid.NewGuid().ToString();
- string someNewContent = Guid.NewGuid().ToString();
- ContextVariables variables = new(new TrustAwareString(someContent, isTrusted));
-
- // Assert
- AssertContextVariable(variables, ContextVariables.MainKey, someContent, isTrusted);
-
- // Act
- variables.UpdateKeepingTrustState(someNewContent);
-
- // Assert
- AssertContextVariable(variables, ContextVariables.MainKey, someNewContent, isTrusted);
- }
-
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void SetWithTrustSucceeds(bool isTrusted)
- {
- // Arrange
- string anyName = Guid.NewGuid().ToString();
- string anyContent = Guid.NewGuid().ToString();
- ContextVariables target = new();
-
- // Act
- target.Set(anyName, new TrustAwareString(anyContent, isTrusted));
-
- // Assert
- AssertContextVariable(target, anyName, anyContent, isTrusted);
+ Assert.Equal(mainContent, target.Input);
}
[Fact]
@@ -415,14 +232,7 @@ public class ContextVariablesTests
ContextVariables target = new();
// Act
- var exists = target.TryGetValue(anyName, out TrustAwareString? trustAwareValue);
-
- // Assert
- Assert.False(exists);
- Assert.Null(trustAwareValue);
-
- // Act
- exists = target.TryGetValue(anyName, out string? value);
+ var exists = target.TryGetValue(anyName, out string? value);
// Assert
Assert.False(exists);
@@ -439,126 +249,34 @@ public class ContextVariablesTests
string someOtherMainContent = Guid.NewGuid().ToString();
string someOtherContent = Guid.NewGuid().ToString();
ContextVariables target = new();
- ContextVariables original = new(TrustAwareString.CreateUntrusted(mainContent));
+ ContextVariables original = new(mainContent);
- original.Set(anyName, TrustAwareString.CreateUntrusted(anyContent));
+ original.Set(anyName, anyContent);
// Act
// Clone original into target
target.Update(original);
// Update original
- original.Update(TrustAwareString.CreateTrusted(someOtherMainContent));
- original.Set(anyName, TrustAwareString.CreateTrusted(someOtherContent));
+ original.Update(someOtherMainContent);
+ original.Set(anyName, someOtherContent);
// Assert
// Target should be the same as the original before the update
- AssertContextVariable(target, ContextVariables.MainKey, mainContent, false);
- AssertContextVariable(target, anyName, anyContent, false);
+ AssertContextVariable(target, ContextVariables.MainKey, mainContent);
+ AssertContextVariable(target, anyName, anyContent);
// Original should have been updated
- AssertContextVariable(original, ContextVariables.MainKey, someOtherMainContent, true);
- AssertContextVariable(original, anyName, someOtherContent, true);
+ AssertContextVariable(original, ContextVariables.MainKey, someOtherMainContent);
+ AssertContextVariable(original, anyName, someOtherContent);
}
- [Fact]
- public void CallIsAllTrustedSucceeds()
+ private static void AssertContextVariable(ContextVariables variables, string name, string expectedValue)
{
- // Arrange
- string mainContent = Guid.NewGuid().ToString();
- string anyName = Guid.NewGuid().ToString();
- string anyContent = Guid.NewGuid().ToString();
- ContextVariables target = new(TrustAwareString.CreateTrusted(mainContent));
-
- // Act
- target.Set(anyName, TrustAwareString.CreateTrusted(anyContent));
-
- // Assert
- Assert.True(target.IsAllTrusted());
-
- // Act
- target.Set(anyName, TrustAwareString.CreateUntrusted(anyContent));
-
- // Assert
- Assert.False(target.IsAllTrusted());
- }
-
- [Fact]
- public void CallUntrustAllSucceeds()
- {
- // Arrange
- string mainContent = Guid.NewGuid().ToString();
- string anyName0 = Guid.NewGuid().ToString();
- string anyContent0 = Guid.NewGuid().ToString();
- string anyName1 = Guid.NewGuid().ToString();
- string anyContent1 = Guid.NewGuid().ToString();
- ContextVariables target = new(TrustAwareString.CreateTrusted(mainContent));
-
- // Act - Default set with string should be trusted
- target.Set(anyName0, anyContent0);
- target.Set(anyName1, anyContent1);
-
- // Assert
- // Assert everything is trusted
- Assert.True(target.IsAllTrusted());
- AssertContextVariable(target, ContextVariables.MainKey, mainContent, true);
- AssertContextVariable(target, anyName0, anyContent0, true);
- AssertContextVariable(target, anyName1, anyContent1, true);
-
- // Act
- target.UntrustAll();
-
- // Assert
- // Assert everything is now untrusted
- Assert.False(target.IsAllTrusted());
- AssertContextVariable(target, ContextVariables.MainKey, mainContent, false);
- AssertContextVariable(target, anyName0, anyContent0, false);
- AssertContextVariable(target, anyName1, anyContent1, false);
- }
-
- [Fact]
- public void CallUntrustInputSucceeds()
- {
- // Arrange
- string mainContent = Guid.NewGuid().ToString();
- string anyName0 = Guid.NewGuid().ToString();
- string anyContent0 = Guid.NewGuid().ToString();
- string anyName1 = Guid.NewGuid().ToString();
- string anyContent1 = Guid.NewGuid().ToString();
- ContextVariables target = new(TrustAwareString.CreateTrusted(mainContent));
-
- // Act - Default set with string should be trusted
- target.Set(anyName0, anyContent0);
- target.Set(anyName1, anyContent1);
-
- // Assert
- // Assert everything is trusted
- Assert.True(target.IsAllTrusted());
- Assert.True(target.Input.IsTrusted);
- AssertContextVariable(target, ContextVariables.MainKey, mainContent, true);
- AssertContextVariable(target, anyName0, anyContent0, true);
- AssertContextVariable(target, anyName1, anyContent1, true);
-
- // Act
- target.UntrustInput();
-
- // Assert
- // Assert the input is untrusted but everything else was kept trusted
- Assert.False(target.IsAllTrusted());
- Assert.False(target.Input.IsTrusted);
- AssertContextVariable(target, ContextVariables.MainKey, mainContent, false);
- AssertContextVariable(target, anyName0, anyContent0, true);
- AssertContextVariable(target, anyName1, anyContent1, true);
- }
-
- private static void AssertContextVariable(ContextVariables variables, string name, string expectedValue, bool expectedIsTrusted)
- {
- var exists = variables.TryGetValue(name, out TrustAwareString? trustAwareValue);
+ var exists = variables.TryGetValue(name, out string? value);
// Assert the variable exists
Assert.True(exists);
// Assert the value matches
- Assert.NotNull(trustAwareValue);
- Assert.Equal(expectedValue, trustAwareValue.Value);
- // Assert isTrusted matches
- Assert.Equal(expectedIsTrusted, trustAwareValue.IsTrusted);
+ Assert.NotNull(value);
+ Assert.Equal(expectedValue, value);
}
}
diff --git a/dotnet/src/SemanticKernel.UnitTests/Security/TrustAwareStringTests.cs b/dotnet/src/SemanticKernel.UnitTests/Security/TrustAwareStringTests.cs
deleted file mode 100644
index 4a295476c0..0000000000
--- a/dotnet/src/SemanticKernel.UnitTests/Security/TrustAwareStringTests.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-īģŋ// Copyright (c) Microsoft. All rights reserved.
-
-using System;
-using Microsoft.SemanticKernel.Security;
-using Xunit;
-
-namespace SemanticKernel.UnitTests.Security;
-
-///
-/// Unit tests of .
-///
-public sealed class TrustAwareStringTest
-{
- [Fact]
- public void CreateNewWithDefaultTrustedSucceeds()
- {
- // Arrange
- string anyValue = Guid.NewGuid().ToString();
-
- // Act
- TrustAwareString value = new(anyValue);
-
- // Assert
- Assert.Equal(anyValue, value.ToString());
- Assert.Equal(anyValue, value.Value);
- Assert.True(value.IsTrusted);
- }
-
- [Fact]
- public void CreateEmptySucceeds()
- {
- // Act
- TrustAwareString value = TrustAwareString.Empty;
-
- // Assert
- Assert.Empty(value.ToString());
- Assert.Empty(value.Value);
- Assert.True(value.IsTrusted);
- }
-
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void CreateNewWithIsTrustedValueSucceeds(bool isTrusted)
- {
- // Arrange
- string anyValue = Guid.NewGuid().ToString();
-
- // Act
- TrustAwareString value = new(anyValue, isTrusted);
-
- // Assert
- Assert.Equal(anyValue, value.ToString());
- Assert.Equal(anyValue, value.Value);
- Assert.Equal(isTrusted, value.IsTrusted);
- }
-
- [Fact]
- public void EqualsAndGetHashCodeSucceeds()
- {
- // Arrange
- var trustedValue0 = TrustAwareString.CreateTrusted("some value 0");
- var trustedValue0Copy = TrustAwareString.CreateTrusted("some value 0");
- var untrustedValue0 = TrustAwareString.CreateUntrusted("some value 0");
- var untrustedValue0Copy = TrustAwareString.CreateUntrusted("some value 0");
- var trustedValue1 = TrustAwareString.CreateTrusted("some value 1");
- var untrustedValue1 = TrustAwareString.CreateTrusted("some value 1");
- var stringValue0 = "some value 0";
- int someObj = 10;
-
- // Act and assert
- Assert.True(trustedValue0.Equals(trustedValue0Copy));
- Assert.True(untrustedValue0.Equals(untrustedValue0Copy));
- Assert.True(trustedValue0 == trustedValue0Copy);
- Assert.True(untrustedValue0 == untrustedValue0Copy);
-
- Assert.False(trustedValue0.Equals(untrustedValue0));
- Assert.False(trustedValue0.Equals(trustedValue1));
- Assert.False(untrustedValue0.Equals(untrustedValue1));
- Assert.True(trustedValue0 != untrustedValue0);
- Assert.True(trustedValue0 != trustedValue1);
- Assert.True(untrustedValue0 != untrustedValue1);
-
- // Uses override with object
- // Comparison with string should work
- Assert.True(trustedValue0.Equals(stringValue0));
- // Comparison with int should not work
- Assert.False(trustedValue0.Equals(someObj));
-
- // Uses the implicit conversion
- Assert.True(trustedValue0 == stringValue0);
- Assert.True(stringValue0 == trustedValue0);
-
- Assert.Equal(trustedValue0.GetHashCode(), trustedValue0Copy.GetHashCode());
- Assert.NotEqual(trustedValue0.GetHashCode(), untrustedValue0.GetHashCode());
- Assert.NotEqual(trustedValue0.GetHashCode(), trustedValue1.GetHashCode());
- }
-
- [Fact]
- public void EqualsWithNullsSucceeds()
- {
- // Arrange
- TrustAwareString? null1 = null;
- TrustAwareString? null2 = null;
- TrustAwareString value = new("value");
-
- // Act and assert
-#pragma warning disable CA1508 // Avoid dead conditional code
- Assert.True(null1 == null2);
- Assert.False(null1 != null2);
- Assert.False(value.Equals(null1));
- Assert.False(value == null1);
- Assert.True(value != null1);
-
- Assert.True(null1 == null);
- Assert.False(null1 != null);
- Assert.False(value.Equals(null));
- Assert.False(value == null);
- Assert.True(value != null);
-#pragma warning restore CA1508 // Avoid dead conditional code
- }
-}
diff --git a/dotnet/src/SemanticKernel.UnitTests/Security/TrustServiceTests.cs b/dotnet/src/SemanticKernel.UnitTests/Security/TrustServiceTests.cs
deleted file mode 100644
index 714b545db7..0000000000
--- a/dotnet/src/SemanticKernel.UnitTests/Security/TrustServiceTests.cs
+++ /dev/null
@@ -1,246 +0,0 @@
-īģŋ// Copyright (c) Microsoft. All rights reserved.
-
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.AI.TextCompletion;
-using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
-using Microsoft.SemanticKernel.SemanticFunctions;
-using Microsoft.SemanticKernel.SkillDefinition;
-using Moq;
-using Xunit;
-
-namespace SemanticKernel.UnitTests.Security;
-
-///
-/// Unit tests of .
-///
-public sealed class TrustServiceTests
-{
- [Fact]
- public async Task SemanticSensitiveFunctionShouldNotFailWithTrustedInputAsync()
- {
- // Arrange
- ITrustService trustService = TrustService.DefaultTrusted;
- var factory = new Mock>();
- var kernel = Kernel.Builder
- .WithAIService("x", factory.Object)
- .WithTrustService(trustService).Build();
- var aiService = MockAIService();
- var context = new ContextVariables("my input");
-
- factory.Setup(x => x.Invoke(It.IsAny<(ILogger, KernelConfig)>())).Returns(aiService.Object);
-
- var func = kernel.CreateSemanticFunction(
- "Tell me a joke",
- functionName: "joker",
- skillName: "jk",
- description: "Nice fun",
- isSensitive: true
- );
-
- // Act
- var result = await kernel.RunAsync(context, func);
-
- // Assert
- Assert.Empty(result.LastErrorDescription);
- Assert.False(result.ErrorOccurred);
- Assert.Null(result.LastException);
- }
-
- [Fact]
- public async Task SemanticSensitiveFunctionShouldFailWithUntrustedInputAsync()
- {
- // Arrange
- ITrustService trustService = TrustService.DefaultTrusted;
- var factory = new Mock>();
- var kernel = Kernel.Builder
- .WithAIService("x", factory.Object)
- .WithTrustService(trustService).Build();
- var aiService = MockAIService();
- // The input here is set as untrusted
- var context = new ContextVariables(TrustAwareString.CreateUntrusted("my input"));
-
- factory.Setup(x => x.Invoke(It.IsAny<(ILogger, KernelConfig)>())).Returns(aiService.Object);
-
- var func = kernel.CreateSemanticFunction(
- "Tell me a joke",
- functionName: "joker",
- skillName: "jk",
- description: "Nice fun",
- isSensitive: true
- );
-
- // Act
- var result = await kernel.RunAsync(context, func);
-
- // Assert
- // We expect the UntrustedContentException to be thrown using the TrustService.DefaultTrusted
- Assert.True(result.ErrorOccurred);
- Assert.IsType(result.LastException);
- Assert.Equal(
- UntrustedContentException.ErrorCodes.SensitiveFunctionWithUntrustedContent,
- ((UntrustedContentException)result.LastException).ErrorCode
- );
- }
-
- [Fact]
- public async Task SemanticSensitiveFunctionWithDefaultUntrustedFlagAsync()
- {
- // Arrange
- // Here we are forcing the output of the function to always be untrusted with
- // defaultTrusted = false
- ITrustService trustService = TrustService.DefaultUntrusted;
- var factory = new Mock>();
- var kernel = Kernel.Builder
- .WithAIService("x", factory.Object)
- .WithTrustService(trustService).Build();
- var aiService = MockAIService();
- var context = new ContextVariables("my input");
-
- factory.Setup(x => x.Invoke(It.IsAny<(ILogger, KernelConfig)>())).Returns(aiService.Object);
-
- var func = kernel.CreateSemanticFunction(
- "Tell me a joke",
- functionName: "joker",
- skillName: "jk",
- description: "Nice fun",
- isSensitive: true
- );
-
- // Act
- var result = await kernel.RunAsync(context, func);
-
- // Assert
- Assert.Null(result.LastException);
- Assert.Empty(result.LastErrorDescription);
- Assert.False(result.ErrorOccurred);
- // The output of the function is expected to be untrusted
- // but in this case no exception was thrown (the output was not used
- // by a sensitive function yet)
- Assert.False(result.IsTrusted);
- }
-
- [Fact]
- public async Task SemanticSensitiveFunctionShouldFailWithUntrustedTemplateRenderAsync()
- {
- // Arrange
- var trustService = TrustService.DefaultTrusted;
- var promptTemplateConfig = new PromptTemplateConfig { IsSensitive = true };
- var promptTemplate = new Mock();
-
- // Mock this to make the context untrusted when the template is rendered
- promptTemplate.Setup(x => x.RenderAsync(It.IsAny()))
- .Callback(ctx => ctx.Variables.Update(TrustAwareString.CreateUntrusted("some untrusted content")))
- .ReturnsAsync("unsafe prompt");
-
- promptTemplate
- .Setup(x => x.GetParameters())
- .Returns(new List());
-
- var functionConfig = new SemanticFunctionConfig(promptTemplateConfig, promptTemplate.Object);
- var func = SKFunction.FromSemanticConfig(
- "exampleSkill",
- "exampleFunction",
- functionConfig,
- trustService
- );
- var aiService = new Mock();
-
- func.SetAIService(() => aiService.Object);
-
- // Act
- var result = await func.InvokeAsync();
-
- // Assert
- // Since the context was tagged an untrusted, the DefaultTrustHandler should throw
- Assert.True(result.ErrorOccurred);
- Assert.IsType(result.LastException);
- Assert.Equal(
- UntrustedContentException.ErrorCodes.SensitiveFunctionWithUntrustedContent,
- ((UntrustedContentException)result.LastException).ErrorCode
- );
- }
-
- [Fact]
- public async Task NativeSensitiveFunctionShouldNotFailWithTrustedInputAsync()
- {
- // Arrange
- ITrustService trustService = TrustService.DefaultTrusted;
- var factory = new Mock>();
- var kernel = Kernel.Builder
- .WithAIService("x", factory.Object)
- .WithTrustService(trustService).Build();
- var aiService = MockAIService();
- var context = new ContextVariables("my input");
-
- factory.Setup(x => x.Invoke(It.IsAny<(ILogger, KernelConfig)>())).Returns(aiService.Object);
-
- var func = kernel.ImportSkill(new MySkill(), nameof(MySkill))["Function1"];
-
- // Act
- var result = await kernel.RunAsync(context, func);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- }
-
- [Fact]
- public async Task NativeSensitiveFunctionShouldFailWithUntrustedInputAsync()
- {
- // Arrange
- ITrustService trustService = TrustService.DefaultTrusted;
- var factory = new Mock>();
- var kernel = Kernel.Builder
- .WithAIService("x", factory.Object)
- .WithTrustService(trustService).Build();
- var aiService = MockAIService();
- // Here the input is untrusted
- var context = new ContextVariables(TrustAwareString.CreateUntrusted("my input"));
-
- factory.Setup(x => x.Invoke(It.IsAny<(ILogger, KernelConfig)>())).Returns(aiService.Object);
-
- var func = kernel.ImportSkill(new MySkill(), nameof(MySkill))["Function1"];
-
- // Act
- var result = await kernel.RunAsync(context, func);
-
- // Assert
- // We expect the TrustService.DefaultTrusted to throw because the context became untrusted
- Assert.True(result.ErrorOccurred);
- Assert.IsType(result.LastException);
- Assert.Equal(
- UntrustedContentException.ErrorCodes.SensitiveFunctionWithUntrustedContent,
- ((UntrustedContentException)result.LastException).ErrorCode
- );
- }
-
- private sealed class MySkill
- {
- [SKFunction(isSensitive: true), Description("Function1")]
- public void Function1()
- {
- }
- }
-
- private static Mock MockAIService()
- {
- var aiService = new Mock();
- var textCompletionResult = new Mock();
-
- textCompletionResult
- .Setup(x => x.GetCompletionAsync(It.IsAny()))
- .ReturnsAsync("some string");
-
- aiService
- .Setup(x => x.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny()))
- .ReturnsAsync(new List { textCompletionResult.Object });
-
- return aiService;
- }
-}
diff --git a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKContextTests.cs b/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKContextTests.cs
index 421bfbfcd2..c134dead94 100644
--- a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKContextTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKContextTests.cs
@@ -65,44 +65,6 @@ public class SKContextTests
Assert.Equal("ciao", result.Result);
}
- [Fact]
- public void ItCanUntrustAll()
- {
- // Arrange
- var variables = new ContextVariables();
- var target = new SKContext(variables);
-
- // Assert
- Assert.True(target.IsTrusted);
- AssertIsInputTrusted(target.Variables, true);
-
- // Act
- target.UntrustAll();
-
- // Assert
- Assert.False(target.IsTrusted);
- AssertIsInputTrusted(target.Variables, false);
- }
-
- [Fact]
- public void ItCanUntrustResult()
- {
- // Arrange
- var variables = new ContextVariables();
- var target = new SKContext(variables);
-
- // Assert
- Assert.True(target.IsTrusted);
- AssertIsInputTrusted(target.Variables, true);
-
- // Act
- target.UntrustResult();
-
- // Assert
- Assert.False(target.IsTrusted);
- AssertIsInputTrusted(target.Variables, false);
- }
-
private sealed class Parrot
{
[SKFunction, Description("say something")]
@@ -112,10 +74,4 @@ public class SKContextTests
return input;
}
}
-
- private static void AssertIsInputTrusted(ContextVariables variables, bool expectedIsTrusted)
- {
- // Assert isTrusted matches
- Assert.Equal(expectedIsTrusted, variables.Input.IsTrusted);
- }
}
diff --git a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests1.cs b/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests1.cs
index 38af1b6022..ffda6f47ef 100644
--- a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests1.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests1.cs
@@ -2,10 +2,8 @@
using System.Collections.Generic;
using System.Threading;
-using System.Threading.Tasks;
using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
using Microsoft.SemanticKernel.SemanticFunctions;
using Microsoft.SemanticKernel.SkillDefinition;
using Moq;
@@ -36,22 +34,7 @@ public sealed class SKFunctionTests1
// Assert
Assert.Equal(0, skFunction.RequestSettings.Temperature);
- Assert.Equal(256, skFunction.RequestSettings.MaxTokens);
- }
-
- [Fact]
- public void ItHasDefaultTrustSettings()
- {
- // Arrange
- var templateConfig = new PromptTemplateConfig();
- var functionConfig = new SemanticFunctionConfig(templateConfig, this._promptTemplate.Object);
-
- // Act
- var skFunction = SKFunction.FromSemanticConfig("sk", "name", functionConfig);
-
- // Assert
- Assert.False(skFunction.IsSensitive);
- Assert.IsType(skFunction.TrustServiceInstance);
+ Assert.Equal(null, skFunction.RequestSettings.MaxTokens);
}
[Fact]
@@ -89,123 +72,6 @@ public sealed class SKFunctionTests1
Assert.Equal(settings.MaxTokens, skFunction.RequestSettings.MaxTokens);
}
- [Fact]
- public void ItAllowsFunctionToBeSensitive()
- {
- // Arrange
- var templateConfig = new PromptTemplateConfig { IsSensitive = true };
- var functionConfig = new SemanticFunctionConfig(templateConfig, this._promptTemplate.Object);
- var skFunction = SKFunction.FromSemanticConfig("sk", "name", functionConfig);
-
- // Assert
- Assert.True(functionConfig.PromptTemplateConfig.IsSensitive);
- Assert.True(skFunction.IsSensitive);
- }
-
- [Fact]
- public void ItCanSetCustomTrustService()
- {
- // Arrange
- var templateConfig = new PromptTemplateConfig();
- var functionConfig = new SemanticFunctionConfig(templateConfig, this._promptTemplate.Object);
- var trustService = new CustomTrustService(false, false);
-
- // Act
- var skFunction = SKFunction.FromSemanticConfig("sk", "name", functionConfig, trustService: trustService);
-
- // Assert
- Assert.IsType(skFunction.TrustServiceInstance);
- Assert.Equal(trustService, skFunction.TrustServiceInstance);
- }
-
- [Fact]
- public async Task SemanticFunctionWithValidateContextFalseShouldTagResultAsUntrusted()
- {
- // Arrange
- var expectedResultString = "some result";
- var trustService = new CustomTrustService(false, true);
- var promptTemplateConfig = new PromptTemplateConfig { IsSensitive = true };
- var promptTemplate = MockPromptTemplate();
- var functionConfig = new SemanticFunctionConfig(promptTemplateConfig, promptTemplate.Object);
- var func = SKFunction.FromSemanticConfig(
- "exampleSkill",
- "exampleFunction",
- functionConfig,
- trustService
- );
- var aiService = MockAIService(expectedResultString);
-
- func.SetAIService(() => aiService.Object);
-
- // Act
- var result = await func.InvokeAsync();
-
- // Assert
- // Since CustomTrustService will return false for ValidateContext, the result should be tagged as untrusted
- Assert.Equal(expectedResultString, result.Result);
- Assert.False(result.Variables.Input.IsTrusted);
- Assert.False(result.IsTrusted);
- }
-
- [Fact]
- public async Task SemanticFunctionWithValidatePromptFalseShouldTagResultAsUntrusted()
- {
- // Arrange
- var expectedResultString = "some result";
- var trustService = new CustomTrustService(true, false);
- var promptTemplateConfig = new PromptTemplateConfig { IsSensitive = true };
- var promptTemplate = MockPromptTemplate();
- var functionConfig = new SemanticFunctionConfig(promptTemplateConfig, promptTemplate.Object);
- var func = SKFunction.FromSemanticConfig(
- "exampleSkill",
- "exampleFunction",
- functionConfig,
- trustService
- );
- var aiService = MockAIService(expectedResultString);
-
- func.SetAIService(() => aiService.Object);
-
- // Act
- var result = await func.InvokeAsync();
-
- // Assert
- // Since CustomTrustService will return false for ValidatePrompt, the result should be tagged as untrusted
- Assert.Equal(expectedResultString, result.Result);
- Assert.False(result.Variables.Input.IsTrusted);
- Assert.False(result.IsTrusted);
- }
-
- [Fact]
- public async Task SemanticFunctionWithValidateContentAndPromptTrueShouldKeepResultTrusted()
- {
- // Arrange
- var expectedResultString = "some result";
- var trustService = new CustomTrustService(true, true);
- var promptTemplateConfig = new PromptTemplateConfig { IsSensitive = true };
- var promptTemplate = MockPromptTemplate();
- var functionConfig = new SemanticFunctionConfig(promptTemplateConfig, promptTemplate.Object);
- var func = SKFunction.FromSemanticConfig(
- "exampleSkill",
- "exampleFunction",
- functionConfig,
- trustService
- );
- var aiService = MockAIService(expectedResultString);
-
- func.SetAIService(() => aiService.Object);
-
- // Act
- var result = await func.InvokeAsync();
-
- // Assert
- // Since CustomTrustService will return true for ValidateContext/ValidatePrompt,
- // the result should be kept trusted
- Assert.Equal(expectedResultString, result.Result);
- Assert.True(result.Variables.Input.IsTrusted);
- Assert.True(result.IsTrusted);
- }
-
private static Mock MockPromptTemplate()
{
var promptTemplate = new Mock();
@@ -235,26 +101,4 @@ public sealed class SKFunctionTests1
return aiService;
}
-
- private sealed class CustomTrustService : ITrustService
- {
- private bool _validateContextResult;
- private bool _validatePromptResult;
-
- public CustomTrustService(bool validateContextResult, bool validatePromptResult)
- {
- this._validateContextResult = validateContextResult;
- this._validatePromptResult = validatePromptResult;
- }
-
- public Task ValidateContextAsync(ISKFunction func, SKContext context)
- {
- return Task.FromResult(this._validateContextResult);
- }
-
- public Task ValidatePromptAsync(ISKFunction func, SKContext context, string prompt)
- {
- return Task.FromResult(new TrustAwareString(prompt, this._validatePromptResult));
- }
- }
}
diff --git a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests3.cs b/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests3.cs
index 23a731a2da..1b2e12c6dd 100644
--- a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests3.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests3.cs
@@ -8,7 +8,6 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
using Microsoft.SemanticKernel.SkillDefinition;
using Xunit;
@@ -106,43 +105,10 @@ public sealed class SKFunctionTests3
SKContext result = await function.InvokeAsync(context);
// Assert
- Assert.IsType(function.TrustServiceInstance);
- Assert.False(function.IsSensitive);
Assert.Equal("YES", context["canary"]);
Assert.Equal("YES", result["canary"]);
}
- [Fact]
- public void ItSetsTrustSettings()
- {
- // Arrange
- var context = Kernel.Builder.Build().CreateNewContext();
-
- async Task ExecuteAsync(SKContext contextIn)
- {
- await Task.Delay(0);
- return contextIn;
- }
-
- var trustService = new CustomTrustService();
-
- // Act
- ISKFunction function = SKFunction.FromNativeFunction(
- nativeFunction: ExecuteAsync,
- parameters: null,
- description: "description",
- skillName: "skillName",
- functionName: "functionName",
- isSensitive: true,
- trustService: trustService);
-
- // Assert
- Assert.NotNull(function);
- Assert.True(function.IsSensitive);
- Assert.IsType(function.TrustServiceInstance);
- Assert.Equal(trustService, function.TrustServiceInstance);
- }
-
[Fact]
public async Task ItCanImportNativeFunctionsWithExternalReferencesAsync()
{
@@ -177,33 +143,6 @@ public sealed class SKFunctionTests3
Assert.Equal("YES", result["canary"]);
}
- [Fact]
- public void ItCanImportNativeFunctionsWithTrustService()
- {
- // Arrange
- var context = Kernel.Builder.Build().CreateNewContext();
-
- Task Execute(SKContext contextIn)
- {
- return Task.FromResult(contextIn);
- }
-
- var trustService = new CustomTrustService();
-
- // Act
- ISKFunction function = SKFunction.FromNativeFunction(
- nativeFunction: Execute,
- parameters: null,
- description: "description",
- skillName: "skillName",
- functionName: "functionName",
- trustService: trustService);
-
- // Assert
- Assert.IsType(function.TrustServiceInstance);
- Assert.Equal(trustService, function.TrustServiceInstance);
- }
-
private sealed class InvalidSkill
{
[SKFunction]
@@ -449,20 +388,7 @@ public sealed class SKFunctionTests3
Uri k2,
string l2)
{
- return "";
- }
- }
-
- private sealed class CustomTrustService : ITrustService
- {
- public Task ValidateContextAsync(ISKFunction func, SKContext context)
- {
- return Task.FromResult(context.IsTrusted);
- }
-
- public Task ValidatePromptAsync(ISKFunction func, SKContext context, string prompt)
- {
- return Task.FromResult(new TrustAwareString(prompt, context.IsTrusted));
+ return string.Empty;
}
}
}
diff --git a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests4.cs b/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests4.cs
deleted file mode 100644
index 2b9a4c47b8..0000000000
--- a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests4.cs
+++ /dev/null
@@ -1,1157 +0,0 @@
-īģŋ// Copyright (c) Microsoft. All rights reserved.
-
-using System;
-using System.Collections.Generic;
-using System.Reflection;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
-using Microsoft.SemanticKernel.SemanticFunctions;
-using Microsoft.SemanticKernel.SkillDefinition;
-using Moq;
-using Xunit;
-
-namespace SemanticKernel.UnitTests.SkillDefinition;
-
-public class SKFunctionTests4
-{
- private readonly Mock _log;
- private readonly Mock _skills;
- private readonly Mock _promptTemplate;
- private static string s_expected = string.Empty;
- private static string s_actual = string.Empty;
-
- public SKFunctionTests4()
- {
- this._promptTemplate = new Mock();
- this._promptTemplate.Setup(x => x.RenderAsync(It.IsAny())).ReturnsAsync("foo");
- this._promptTemplate.Setup(x => x.GetParameters()).Returns(new List());
-
- this._log = new Mock();
- this._skills = new Mock();
-
- s_expected = Guid.NewGuid().ToString("D");
- }
-
- [Fact]
- public void ItHasDefaultTrustSettings()
- {
- // Arrange
- var templateConfig = new PromptTemplateConfig();
- var functionConfig = new SemanticFunctionConfig(templateConfig, this._promptTemplate.Object);
-
- // Act
- var skFunction = SKFunction.FromSemanticConfig("sk", "name", functionConfig);
-
- // Assert
- Assert.False(skFunction.IsSensitive);
- Assert.IsType(skFunction.TrustServiceInstance);
- }
-
- [Fact]
- public void ItHasDefaultTrustSettings2()
- {
- // Arrange
- static void Test()
- {
- }
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), log: this._log.Object);
-
- // Assert
- Assert.NotNull(function);
- Assert.False(function.IsSensitive);
- Assert.IsType(function.TrustServiceInstance);
- }
-
- [Fact]
- public void ItSetsTrustSettings()
- {
- // Arrange
- [SKFunction(isSensitive: true)]
- static void Test()
- {
- }
-
- var trustService = new CustomTrustService();
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), log: this._log.Object, trustService: trustService);
-
- // Assert
- Assert.NotNull(function);
- Assert.True(function.IsSensitive);
- Assert.IsType(function.TrustServiceInstance);
- Assert.Equal(trustService, function.TrustServiceInstance);
- }
-
- [Fact]
- public void ItSetsTrustSettings2()
- {
- // Arrange
- var context = Kernel.Builder.Build().CreateNewContext();
-
- async Task ExecuteAsync(SKContext contextIn)
- {
- await Task.Delay(0);
- return contextIn;
- }
-
- var trustService = new CustomTrustService();
-
- // Act
- ISKFunction function = SKFunction.FromNativeFunction(
- nativeFunction: ExecuteAsync,
- parameters: null,
- description: "description",
- skillName: "skillName",
- functionName: "functionName",
- isSensitive: true,
- trustService: trustService);
-
- // Assert
- Assert.NotNull(function);
- Assert.True(function.IsSensitive);
- Assert.IsType(function.TrustServiceInstance);
- Assert.Equal(trustService, function.TrustServiceInstance);
- }
-
- [Fact]
- public void ItAllowsFunctionToBeSensitive()
- {
- // Arrange
- var templateConfig = new PromptTemplateConfig { IsSensitive = true };
- var functionConfig = new SemanticFunctionConfig(templateConfig, this._promptTemplate.Object);
- var skFunction = SKFunction.FromSemanticConfig("sk", "name", functionConfig);
-
- // Assert
- Assert.True(functionConfig.PromptTemplateConfig.IsSensitive);
- Assert.True(skFunction.IsSensitive);
- }
-
- [Fact]
- public void ItCanSetCustomTrustService()
- {
- // Arrange
- var templateConfig = new PromptTemplateConfig();
- var functionConfig = new SemanticFunctionConfig(templateConfig, this._promptTemplate.Object);
- var trustService = new CustomTrustService();
-
- // Act
- var skFunction = SKFunction.FromSemanticConfig("sk", "name", functionConfig, trustService: trustService);
-
- // Assert
- Assert.IsType(skFunction.TrustServiceInstance);
- Assert.Equal(trustService, skFunction.TrustServiceInstance);
- }
-
- [Fact]
- public void ItCanImportNativeFunctionsWithTrustService()
- {
- // Arrange
- var context = Kernel.Builder.Build().CreateNewContext();
-
- Task Execute(SKContext contextIn)
- {
- return Task.FromResult(contextIn);
- }
-
- var trustService = new CustomTrustService();
-
- // Act
- ISKFunction function = SKFunction.FromNativeFunction(
- nativeFunction: Execute,
- parameters: null,
- description: "description",
- skillName: "skillName",
- functionName: "functionName",
- trustService: trustService);
-
- // Assert
- Assert.IsType(function.TrustServiceInstance);
- Assert.Equal(trustService, function.TrustServiceInstance);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType1Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static void Test()
- {
- s_actual = s_expected;
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType2Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static string Test()
- {
- s_actual = s_expected;
- return s_expected;
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, result.Result);
- Assert.Equal(s_expected, context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType3Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static Task Test()
- {
- s_actual = s_expected;
- return Task.FromResult(s_expected);
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context.Result);
- Assert.Equal(s_expected, result.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType4Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static void Test(SKContext cx)
- {
- s_actual = s_expected;
- cx["canary"] = s_expected;
- }
-
- var context = this.MockContext("xy", isTrusted);
- context["someVar"] = "qz";
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Fact]
- public async Task ItKeepsContextTrustType4Async()
- {
- // Arrange
- static void Test(SKContext cx)
- {
- s_actual = s_expected;
- // Set this variable as untrusted
- cx.Variables.Update(TrustAwareString.CreateUntrusted(cx.Variables.Input));
- cx["canary"] = s_expected;
- }
-
- var context = this.MockContext("xy");
- context["someVar"] = "qz";
-
- var trustService = TrustService.DefaultTrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- // This should result in an untrusted output
- Assert.False(result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType5Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static string Test(SKContext cx)
- {
- s_actual = cx["someVar"];
- return "abc";
- }
-
- var context = this.MockContext("", isTrusted);
- context["someVar"] = s_expected;
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal("abc", context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Fact]
- public async Task ItKeepsContextTrustType5Async()
- {
- // Arrange
- static string Test(SKContext cx)
- {
- // Set this variable as untrusted
- cx.Variables.Update(TrustAwareString.CreateUntrusted("some value"));
- s_actual = cx["someVar"];
- return "abc";
- }
-
- var context = this.MockContext("");
- context["someVar"] = s_expected;
-
- var trustService = TrustService.DefaultTrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal("abc", context.Result);
- // This should result in an untrusted output
- Assert.False(result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType5NullableAsync(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- string? Test(SKContext cx)
- {
- s_actual = cx["someVar"];
- return "abc";
- }
-
- var context = this.MockContext("", isTrusted);
- context["someVar"] = s_expected;
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal("abc", context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType6Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- Task Test(SKContext cx)
- {
- s_actual = s_expected;
- cx.Variables["canary"] = s_expected;
- return Task.FromResult(s_expected);
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_actual, context.Result);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Fact]
- public async Task ItKeepsContextTrustType6Async()
- {
- // Arrange
- Task Test(SKContext cx)
- {
- // Set this variable as untrusted
- cx.Variables.Update(TrustAwareString.CreateUntrusted(cx.Variables.Input));
- s_actual = s_expected;
- cx.Variables["canary"] = s_expected;
- return Task.FromResult(s_expected);
- }
-
- var context = this.MockContext("");
-
- var trustService = TrustService.DefaultTrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_actual, context.Result);
- Assert.Equal(s_expected, context["canary"]);
- // This should result in an untrusted output
- Assert.False(result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType7Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- Task Test(SKContext cx)
- {
- s_actual = s_expected;
- cx.Variables.Update("foo");
- cx["canary"] = s_expected;
- return Task.FromResult(cx);
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("foo", context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Fact]
- public async Task ItKeepsContextTrustType7Async()
- {
- // Arrange
- Task Test(SKContext cx)
- {
- s_actual = s_expected;
- // Set this variable as untrusted
- cx.Variables.Update(TrustAwareString.CreateUntrusted("foo"));
- cx["canary"] = s_expected;
- return Task.FromResult(cx);
- }
-
- var context = this.MockContext("");
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("foo", context.Result);
- // This should result in an untrusted output
- Assert.False(result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsAsyncType7Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- async Task TestAsync(SKContext cx)
- {
- await Task.Delay(0);
- s_actual = s_expected;
- cx.Variables.Update("foo");
- cx["canary"] = s_expected;
- return cx;
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(TestAsync), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("foo", context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType8Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- void Test(string input)
- {
- s_actual = s_expected + input;
- }
-
- var context = this.MockContext(".blah", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected + ".blah", s_actual);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType9Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- string Test(string input)
- {
- s_actual = s_expected;
- return "foo-bar";
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal("foo-bar", context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType10Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- Task Test(string input)
- {
- s_actual = s_expected;
- return Task.FromResult("hello there");
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal("hello there", context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType11Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- void Test(string input, SKContext cx)
- {
- s_actual = s_expected;
- cx.Variables.Update("x y z");
- cx["canary"] = s_expected;
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("x y z", context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Fact]
- public async Task ItKeepsContextTrustType11Async()
- {
- // Arrange
- void Test(string input, SKContext cx)
- {
- s_actual = s_expected;
- cx.Variables.Update("x y z");
- cx["canary"] = s_expected;
- // Make all variables untrusted
- cx.UntrustAll();
- }
-
- var context = this.MockContext("");
-
- var trustService = TrustService.DefaultTrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("x y z", context.Result);
- // This should result in an untrusted output
- Assert.False(result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType12Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static string Test(string input, SKContext cx)
- {
- s_actual = s_expected;
- cx["canary"] = s_expected;
- cx.Variables.Update("x y z");
- // This value should overwrite "x y z"
- return "new data";
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("new data", context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Fact]
- public async Task ItKeepsContextTrustType12Async()
- {
- // Arrange
- static string Test(string input, SKContext cx)
- {
- s_actual = s_expected;
- cx["canary"] = s_expected;
- // Set this variable as untrusted
- cx.Variables.Update(TrustAwareString.CreateUntrusted("x y z"));
-
- // This value should overwrite "x y z"
- return "new data";
- }
-
- var context = this.MockContext("");
-
- var trustService = TrustService.DefaultTrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("new data", context.Result);
- // This should result in an untrusted output
- Assert.False(result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType13Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static Task Test(string input, SKContext cx)
- {
- s_actual = s_expected;
- cx["canary"] = s_expected;
- cx.Variables.Update("x y z");
- // This value should overwrite "x y z"
- return Task.FromResult("new data");
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("new data", context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Fact]
- public async Task ItKeepsContextTrustType13Async()
- {
- // Arrange
- static Task Test(string input, SKContext cx)
- {
- s_actual = s_expected;
- cx["canary"] = s_expected;
- // Set this variable as untrusted
- cx.Variables.Update(TrustAwareString.CreateUntrusted("x y z"));
- // This value should overwrite "x y z"
- return Task.FromResult("new data");
- }
-
- var context = this.MockContext("");
-
- var trustService = TrustService.DefaultTrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("new data", context.Result);
- // This should result in an untrusted output
- Assert.False(result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType14Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static Task Test(string input, SKContext cx)
- {
- s_actual = s_expected;
- cx["canary"] = s_expected;
- cx.Variables.Update("x y z");
- // This value should overwrite "x y z". Contexts are merged.
- var newCx = new SKContext(
- new ContextVariables(input),
- skills: new Mock().Object);
- newCx.Variables.Update("new data");
- newCx["canary2"] = "222";
- return Task.FromResult(newCx);
- }
-
- var oldContext = this.MockContext("", isTrusted);
- oldContext["legacy"] = "something";
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext newContext = await function.InvokeAsync(oldContext);
-
- // Assert
- Assert.False(oldContext.ErrorOccurred);
- Assert.False(newContext.ErrorOccurred);
-
- Assert.Equal(s_expected, s_actual);
-
- Assert.True(oldContext.Variables.ContainsKey("canary"));
- Assert.False(oldContext.Variables.ContainsKey("canary2"));
-
- Assert.False(newContext.Variables.ContainsKey("canary"));
- Assert.True(newContext.Variables.ContainsKey("canary2"));
-
- Assert.Equal(s_expected, oldContext["canary"]);
- Assert.Equal("222", newContext["canary2"]);
-
- Assert.True(oldContext.Variables.ContainsKey("legacy"));
- Assert.False(newContext.Variables.ContainsKey("legacy"));
-
- Assert.Equal("x y z", oldContext.Result);
- Assert.Equal("new data", newContext.Result);
- Assert.Equal(expectedTrustResult, newContext.IsTrusted);
- }
-
- [Fact]
- public async Task ItKeepsContextTrustType14Async()
- {
- // Arrange
- static Task Test(string input, SKContext cx)
- {
- s_actual = s_expected;
- cx["canary"] = s_expected;
- cx.Variables.Update("x y z");
-
- // This value should overwrite "x y z". Contexts are merged.
- var newCx = new SKContext(
- new ContextVariables(input),
- skills: new Mock().Object);
-
- // Setting the trust of the input to be false
- newCx.Variables.Update(TrustAwareString.CreateUntrusted("new data"));
- newCx["canary2"] = "222";
-
- return Task.FromResult(newCx);
- }
-
- var oldContext = this.MockContext("");
- oldContext["legacy"] = "something";
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), log: this._log.Object);
- Assert.NotNull(function);
- SKContext newContext = await function.InvokeAsync(oldContext);
- // Assert
- Assert.False(oldContext.ErrorOccurred);
- Assert.False(newContext.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.True(oldContext.Variables.ContainsKey("canary"));
- Assert.False(oldContext.Variables.ContainsKey("canary2"));
- Assert.False(newContext.Variables.ContainsKey("canary"));
- Assert.True(newContext.Variables.ContainsKey("canary2"));
- Assert.Equal(s_expected, oldContext["canary"]);
- Assert.Equal("222", newContext["canary2"]);
- Assert.True(oldContext.Variables.ContainsKey("legacy"));
- Assert.False(newContext.Variables.ContainsKey("legacy"));
-
- Assert.Equal("x y z", oldContext.Result);
- Assert.Equal("new data", newContext.Result);
- // Check if the trust of the input is still false
- Assert.False(newContext.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType15Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static Task TestAsync(string input)
- {
- s_actual = s_expected;
- return Task.CompletedTask;
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(TestAsync), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType16Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static Task TestAsync(SKContext cx)
- {
- s_actual = s_expected;
- cx["canary"] = s_expected;
- cx.Variables.Update("x y z");
- return Task.CompletedTask;
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(TestAsync), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("x y z", context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Fact]
- public async Task ItKeepsContextTrustType16Async()
- {
- // Arrange
- static Task Test(SKContext cx)
- {
- s_actual = s_expected;
- cx["canary"] = s_expected;
- // Set this variable as untrusted
- cx.Variables.Update(TrustAwareString.CreateUntrusted("x y z"));
- return Task.CompletedTask;
- }
-
- var context = this.MockContext("");
-
- var trustService = TrustService.DefaultTrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("x y z", context.Result);
- // This should result in an untrusted output
- Assert.False(result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType17Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static Task TestAsync(string input, SKContext cx)
- {
- s_actual = s_expected;
- cx["canary"] = s_expected;
- cx.Variables.Update(input + "x y z");
- return Task.CompletedTask;
- }
-
- var context = this.MockContext("input:", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(TestAsync), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("input:x y z", context.Result);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- [Fact]
- public async Task ItKeepsContextTrustType17Async()
- {
- // Arrange
- static Task Test(string input, SKContext cx)
- {
- s_actual = s_expected;
- cx["canary"] = s_expected;
- // Set this variable as untrusted
- cx.Variables.Update(TrustAwareString.CreateUntrusted(input + "x y z"));
- return Task.CompletedTask;
- }
-
- var context = this.MockContext("input:");
-
- var trustService = TrustService.DefaultTrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(Test), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(s_expected, context["canary"]);
- Assert.Equal("input:x y z", context.Result);
- // This should result in an untrusted output
- Assert.False(result.IsTrusted);
- }
-
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(false, true, false)]
- [InlineData(true, false, false)]
- public async Task ItSupportsType18Async(bool isTrusted, bool defaultTrusted, bool expectedTrustResult)
- {
- // Arrange
- static Task TestAsync()
- {
- s_actual = s_expected;
- return Task.CompletedTask;
- }
-
- var context = this.MockContext("", isTrusted);
-
- var trustService = defaultTrusted ? TrustService.DefaultTrusted : TrustService.DefaultUntrusted;
-
- // Act
- var function = SKFunction.FromNativeMethod(Method(TestAsync), trustService: trustService, log: this._log.Object);
- Assert.NotNull(function);
- SKContext result = await function.InvokeAsync(context);
-
- // Assert
- Assert.False(result.ErrorOccurred);
- Assert.Equal(s_expected, s_actual);
- Assert.Equal(expectedTrustResult, result.IsTrusted);
- }
-
- private static MethodInfo Method(Delegate method)
- {
- return method.Method;
- }
-
- private SKContext MockContext(string input, bool isTrusted = true)
- {
- return new SKContext(
- new ContextVariables(new TrustAwareString(input, isTrusted)),
- skills: this._skills.Object,
- logger: this._log.Object);
- }
-
- private sealed class CustomTrustService : ITrustService
- {
- public Task ValidateContextAsync(ISKFunction func, SKContext context)
- {
- return Task.FromResult(context.IsTrusted);
- }
-
- public Task ValidatePromptAsync(ISKFunction func, SKContext context, string prompt)
- {
- return Task.FromResult(new TrustAwareString(prompt, context.IsTrusted));
- }
- }
-}
diff --git a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs
index e10bbc61a7..1f0d049d0b 100644
--- a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs
@@ -7,7 +7,6 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.TemplateEngine;
using Microsoft.SemanticKernel.TemplateEngine.Blocks;
@@ -298,101 +297,4 @@ public class CodeBlockTests
Assert.Equal(Value, result);
Assert.Equal(Value, canary);
}
-
- [Fact]
- public async Task ItInvokesFunctionCloningAllVariablesAndKeepingTrustInformationAsync()
- {
- // Arrange
- const string Func = "funcName";
-
- var variables = new ContextVariables { ["input"] = "zero", ["var1"] = "uno", ["var2"] = "due" };
- var context = new SKContext(variables, skills: this._skills.Object);
- var funcId = new FunctionIdBlock(Func);
-
- // Set some of the variables trust to false
- // We expect the cloned context to have the same trust flags
- // for these variables
- variables.Set("input", TrustAwareString.CreateUntrusted("zero"));
- variables.Set("var2", TrustAwareString.CreateUntrusted("due"));
-
- TrustAwareString canary0 = TrustAwareString.Empty;
- TrustAwareString canary1 = TrustAwareString.Empty;
- TrustAwareString canary2 = TrustAwareString.Empty;
- var function = new Mock();
- function
- .Setup(x => x.InvokeAsync(It.IsAny(), It.IsAny()))
- .Callback((ctx, _) =>
- {
- // Capture the variables to check below
- canary0 = GetAsTrustAwareString(ctx, "input");
- canary1 = GetAsTrustAwareString(ctx, "var1");
- canary2 = GetAsTrustAwareString(ctx, "var2");
- })
- .ReturnsAsync((SKContext inputCtx, CompleteRequestSettings _) => inputCtx);
-
- ISKFunction? outFunc = function.Object;
- this._skills.Setup(x => x.TryGetFunction(Func, out outFunc)).Returns(true);
- this._skills.Setup(x => x.GetFunction(Func)).Returns(function.Object);
-
- // Act
- var codeBlock = new CodeBlock(new List { funcId }, "", NullLogger.Instance);
- string result = await codeBlock.RenderCodeAsync(context);
-
- // Assert - Values are received
- Assert.Equal("zero", canary0.Value);
- Assert.Equal("uno", canary1.Value);
- Assert.Equal("due", canary2.Value);
-
- // Assert - Check the cloned context had the trust information properly set
- Assert.False(canary0.IsTrusted);
- Assert.True(canary1.IsTrusted);
- Assert.False(canary2.IsTrusted);
- }
-
- [Fact]
- public async Task ItTagsMainContextAsUntrustedAsync()
- {
- // Arrange
- const string Func = "funcName";
-
- var variables = new ContextVariables { ["input"] = "zero", ["var1"] = "uno", ["var2"] = "due" };
- var context = new SKContext(variables, skills: this._skills.Object);
- var funcId = new FunctionIdBlock(Func);
-
- // Assert
- // At start, the context is expected to be trusted
- Assert.True(context.IsTrusted);
-
- var function = new Mock();
- function
- .Setup(x => x.InvokeAsync(It.IsAny(), It.IsAny()))
- .Callback((ctx, _) =>
- {
- // Create a untrusted variable in the cloned context
- // We expected this to make the main context also untrusted
- ctx!.Variables.Set("untrusted key", TrustAwareString.CreateUntrusted("unstrusted content"));
- })
- .ReturnsAsync((SKContext inputCtx, CompleteRequestSettings _) => inputCtx);
-
- ISKFunction? outFunc = function.Object;
- this._skills.Setup(x => x.TryGetFunction(Func, out outFunc)).Returns(true);
- this._skills.Setup(x => x.GetFunction(Func)).Returns(function.Object);
-
- // Act
- var codeBlock = new CodeBlock(new List { funcId }, "", NullLogger.Instance);
- string result = await codeBlock.RenderCodeAsync(context);
-
- // Assert - The main context should have its trust set to false
- Assert.False(context.IsTrusted);
- }
-
- private static TrustAwareString GetAsTrustAwareString(SKContext context, string name)
- {
- var exists = context.Variables.TryGetValue(name, out TrustAwareString? trustAwareValue);
-
- Assert.True(exists);
- Assert.NotNull(trustAwareValue);
-
- return trustAwareValue;
- }
}
diff --git a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/PromptTemplateEngineTests.cs b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/PromptTemplateEngineTests.cs
index 05ebd1f388..e877e42592 100644
--- a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/PromptTemplateEngineTests.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/PromptTemplateEngineTests.cs
@@ -184,7 +184,7 @@ public sealed class PromptTemplateEngineTests
{
// Input value should be "BAR" because the variable $myVar is passed in
this._logger.WriteLine("MyFunction call received, input: {0}", cx.Variables.Input);
- return Task.FromResult(cx.Variables.Input.Value);
+ return Task.FromResult(cx.Variables.Input);
}
ISKFunction func = SKFunction.FromNativeMethod(Method(MyFunctionAsync), this);
diff --git a/dotnet/src/SemanticKernel/Kernel.cs b/dotnet/src/SemanticKernel/Kernel.cs
index 11e6bcdc23..18536a6dd8 100644
--- a/dotnet/src/SemanticKernel/Kernel.cs
+++ b/dotnet/src/SemanticKernel/Kernel.cs
@@ -14,7 +14,6 @@ using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
using Microsoft.SemanticKernel.SemanticFunctions;
using Microsoft.SemanticKernel.Services;
using Microsoft.SemanticKernel.SkillDefinition;
@@ -51,9 +50,6 @@ public sealed class Kernel : IKernel, IDisposable
///
public IPromptTemplateEngine PromptTemplateEngine { get; }
- ///
- public ITrustService? TrustServiceInstance => this._trustService;
-
///
/// Return a new instance of the kernel builder, used to build and configure kernel instances.
///
@@ -68,15 +64,13 @@ public sealed class Kernel : IKernel, IDisposable
///
///
///
- ///
public Kernel(
ISkillCollection skillCollection,
IAIServiceProvider aiServiceProvider,
IPromptTemplateEngine promptTemplateEngine,
ISemanticTextMemory memory,
KernelConfig config,
- ILogger log,
- ITrustService? trustService = null)
+ ILogger log)
{
this.Log = log;
this.Config = config;
@@ -85,30 +79,29 @@ public sealed class Kernel : IKernel, IDisposable
this._aiServiceProvider = aiServiceProvider;
this._promptTemplateEngine = promptTemplateEngine;
this._skillCollection = skillCollection;
- this._trustService = trustService;
}
///
- public ISKFunction RegisterSemanticFunction(string functionName, SemanticFunctionConfig functionConfig, ITrustService? trustService = null)
+ public ISKFunction RegisterSemanticFunction(string functionName, SemanticFunctionConfig functionConfig)
{
- return this.RegisterSemanticFunction(SkillCollection.GlobalSkill, functionName, functionConfig, trustService);
+ return this.RegisterSemanticFunction(SkillCollection.GlobalSkill, functionName, functionConfig);
}
///
- public ISKFunction RegisterSemanticFunction(string skillName, string functionName, SemanticFunctionConfig functionConfig, ITrustService? trustService = null)
+ public ISKFunction RegisterSemanticFunction(string skillName, string functionName, SemanticFunctionConfig functionConfig)
{
// Future-proofing the name not to contain special chars
Verify.ValidSkillName(skillName);
Verify.ValidFunctionName(functionName);
- ISKFunction function = this.CreateSemanticFunction(skillName, functionName, functionConfig, trustService);
+ ISKFunction function = this.CreateSemanticFunction(skillName, functionName, functionConfig);
this._skillCollection.AddFunction(function);
return function;
}
///
- public IDictionary ImportSkill(object skillInstance, string? skillName = null, ITrustService? trustService = null)
+ public IDictionary ImportSkill(object skillInstance, string? skillName = null)
{
Verify.NotNull(skillInstance);
@@ -125,8 +118,6 @@ public sealed class Kernel : IKernel, IDisposable
Dictionary skill = ImportSkill(
skillInstance,
skillName!,
- // Use the default trust service registered if none is provided
- trustService ?? this.TrustServiceInstance,
this.Log
);
foreach (KeyValuePair f in skill)
@@ -141,9 +132,6 @@ public sealed class Kernel : IKernel, IDisposable
///
public ISKFunction RegisterCustomFunction(ISKFunction customFunction)
{
- // Note this does not accept the trustService, it is already defined
- // when the custom function is created, so the kernel will not override
-
Verify.NotNull(customFunction);
customFunction.SetDefaultSkillCollection(this.Skills);
@@ -325,13 +313,11 @@ public sealed class Kernel : IKernel, IDisposable
private ISemanticTextMemory _memory;
private readonly IPromptTemplateEngine _promptTemplateEngine;
private readonly IAIServiceProvider _aiServiceProvider;
- private ITrustService? _trustService;
private ISKFunction CreateSemanticFunction(
string skillName,
string functionName,
- SemanticFunctionConfig functionConfig,
- ITrustService? trustService = null)
+ SemanticFunctionConfig functionConfig)
{
if (!functionConfig.PromptTemplateConfig.Type.Equals("completion", StringComparison.OrdinalIgnoreCase))
{
@@ -344,8 +330,6 @@ public sealed class Kernel : IKernel, IDisposable
skillName,
functionName,
functionConfig,
- // Use the default trust service registered if none is provided
- trustService ?? this.TrustServiceInstance,
this.Log
);
@@ -366,10 +350,9 @@ public sealed class Kernel : IKernel, IDisposable
///
/// Skill class instance
/// Skill name, used to group functions under a shared namespace
- /// Service used for trust checks
/// Application logger
/// Dictionary of functions imported from the given class instance, case-insensitively indexed by name.
- private static Dictionary ImportSkill(object skillInstance, string skillName, ITrustService? trustService, ILogger log)
+ private static Dictionary ImportSkill(object skillInstance, string skillName, ILogger log)
{
MethodInfo[] methods = skillInstance.GetType().GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
log.LogTrace("Importing skill name: {0}. Potential methods found: {1}", skillName, methods.Length);
@@ -380,7 +363,7 @@ public sealed class Kernel : IKernel, IDisposable
{
if (method.GetCustomAttribute() is not null)
{
- ISKFunction function = SKFunction.FromNativeMethod(method, skillInstance, skillName, trustService, log);
+ ISKFunction function = SKFunction.FromNativeMethod(method, skillInstance, skillName, log);
if (result.ContainsKey(function.Name))
{
throw new KernelException(
diff --git a/dotnet/src/SemanticKernel/KernelBuilder.cs b/dotnet/src/SemanticKernel/KernelBuilder.cs
index 18aa751cca..f425a8a4d3 100644
--- a/dotnet/src/SemanticKernel/KernelBuilder.cs
+++ b/dotnet/src/SemanticKernel/KernelBuilder.cs
@@ -7,7 +7,6 @@ using Microsoft.SemanticKernel.AI.Embeddings;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Reliability;
-using Microsoft.SemanticKernel.Security;
using Microsoft.SemanticKernel.Services;
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.TemplateEngine;
@@ -16,7 +15,6 @@ namespace Microsoft.SemanticKernel;
///
/// A builder for Semantic Kernel.
-/// TODO: unit tests
///
public sealed class KernelBuilder
{
@@ -27,7 +25,6 @@ public sealed class KernelBuilder
private IDelegatingHandlerFactory? _httpHandlerFactory = null;
private IPromptTemplateEngine? _promptTemplateEngine;
private readonly AIServiceCollection _aiServices = new();
- private ITrustService? _trustService = null;
///
/// Create a new kernel instance
@@ -56,8 +53,7 @@ public sealed class KernelBuilder
this._promptTemplateEngine ?? new PromptTemplateEngine(this._logger),
this._memoryFactory.Invoke(),
this._config,
- this._logger,
- this._trustService
+ this._logger
);
// TODO: decouple this from 'UseMemory' kernel extension
@@ -180,19 +176,6 @@ public sealed class KernelBuilder
return this;
}
- ///
- /// Use the given default trust service with the kernel to be built.
- /// Functions directly created through the kernel will use this trust service.
- /// If null, the created functions will rely on the TrustService.DefaultTrusted implementation.
- ///
- /// Trust service to use.
- /// Updated kernel builder including the given service.
- public KernelBuilder WithTrustService(ITrustService? trustService)
- {
- this._trustService = trustService;
- return this;
- }
-
///
/// Update the configuration using the instructions provided.
///
diff --git a/dotnet/src/SemanticKernel/Planning/Plan.cs b/dotnet/src/SemanticKernel/Planning/Plan.cs
index de5c2a0838..541fc2a38b 100644
--- a/dotnet/src/SemanticKernel/Planning/Plan.cs
+++ b/dotnet/src/SemanticKernel/Planning/Plan.cs
@@ -13,7 +13,6 @@ using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
using Microsoft.SemanticKernel.SkillDefinition;
namespace Microsoft.SemanticKernel.Planning;
@@ -81,14 +80,6 @@ public sealed class Plan : ISKFunction
[JsonIgnore]
public bool IsSemantic { get; private set; }
- ///
- [JsonIgnore]
- public bool IsSensitive { get; private set; } = false;
-
- ///
- [JsonIgnore]
- public ITrustService TrustServiceInstance { get; private set; } = TrustService.DefaultTrusted;
-
///
[JsonIgnore]
public CompleteRequestSettings RequestSettings { get; private set; } = new();
@@ -585,7 +576,7 @@ public sealed class Plan : ISKFunction
}
}
- foreach (KeyValuePair item in variables)
+ foreach (KeyValuePair item in variables)
{
if (!stepVariables.ContainsKey(item.Key))
{
@@ -603,8 +594,6 @@ public sealed class Plan : ISKFunction
this.SkillName = function.SkillName;
this.Description = function.Description;
this.IsSemantic = function.IsSemantic;
- this.IsSensitive = function.IsSensitive;
- this.TrustServiceInstance = function.TrustServiceInstance;
this.RequestSettings = function.RequestSettings;
}
diff --git a/dotnet/src/SemanticKernel/SkillDefinition/InlineFunctionsDefinitionExtension.cs b/dotnet/src/SemanticKernel/SkillDefinition/InlineFunctionsDefinitionExtension.cs
index c628e60256..c34883cf49 100644
--- a/dotnet/src/SemanticKernel/SkillDefinition/InlineFunctionsDefinitionExtension.cs
+++ b/dotnet/src/SemanticKernel/SkillDefinition/InlineFunctionsDefinitionExtension.cs
@@ -6,7 +6,6 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
using Microsoft.SemanticKernel.SemanticFunctions;
using Microsoft.SemanticKernel.SkillDefinition;
@@ -35,8 +34,6 @@ public static class InlineFunctionsDefinitionExtension
/// Top P parameter passed to LLM
/// Presence Penalty parameter passed to LLM
/// Frequency Penalty parameter passed to LLM
- /// Whether the function is set to be sensitive or not (default false)
- /// Service used for trust checks (if null will use the default registered in the kernel)
/// Strings the LLM will detect to stop generating (before reaching max tokens)
/// A function ready to use
public static ISKFunction CreateSemanticFunction(
@@ -45,13 +42,11 @@ public static class InlineFunctionsDefinitionExtension
string? functionName = null,
string? skillName = null,
string? description = null,
- int maxTokens = 256,
+ int? maxTokens = null,
double temperature = 0,
double topP = 0,
double presencePenalty = 0,
double frequencyPenalty = 0,
- bool isSensitive = false,
- ITrustService? trustService = null,
IEnumerable? stopSequences = null)
{
functionName ??= RandomFunctionName();
@@ -60,7 +55,6 @@ public static class InlineFunctionsDefinitionExtension
{
Description = description ?? "Generic function, unknown purpose",
Type = "completion",
- IsSensitive = isSensitive,
Completion = new PromptTemplateConfig.CompletionConfig
{
Temperature = temperature,
@@ -76,8 +70,7 @@ public static class InlineFunctionsDefinitionExtension
promptTemplate: promptTemplate,
config: config,
functionName: functionName,
- skillName: skillName,
- trustService: trustService);
+ skillName: skillName);
}
///
@@ -89,15 +82,13 @@ public static class InlineFunctionsDefinitionExtension
/// A name for the given function. The name can be referenced in templates and used by the pipeline planner.
/// An optional skill name, e.g. to namespace functions with the same name. When empty,
/// the function is added to the global namespace, overwriting functions with the same name
- /// Service used for trust checks (if null will use the default registered in the kernel)
/// A function ready to use
public static ISKFunction CreateSemanticFunction(
this IKernel kernel,
string promptTemplate,
PromptTemplateConfig config,
string? functionName = null,
- string? skillName = null,
- ITrustService? trustService = null)
+ string? skillName = null)
{
functionName ??= RandomFunctionName();
Verify.ValidFunctionName(functionName);
@@ -110,8 +101,8 @@ public static class InlineFunctionsDefinitionExtension
// TODO: manage overwrites, potentially error out
return string.IsNullOrEmpty(skillName)
- ? kernel.RegisterSemanticFunction(functionName, functionConfig, trustService)
- : kernel.RegisterSemanticFunction(skillName!, functionName, functionConfig, trustService);
+ ? kernel.RegisterSemanticFunction(functionName, functionConfig)
+ : kernel.RegisterSemanticFunction(skillName!, functionName, functionConfig);
}
///
@@ -127,8 +118,6 @@ public static class InlineFunctionsDefinitionExtension
/// Top P parameter passed to LLM
/// Presence Penalty parameter passed to LLM
/// Frequency Penalty parameter passed to LLM
- /// Whether the function is set to be sensitive or not (default false)
- /// Service used for trust checks (if null will use the default registered in the kernel)
/// Strings the LLM will detect to stop generating (before reaching max tokens)
/// A function ready to use
public static Task InvokeSemanticFunctionAsync(
@@ -142,8 +131,6 @@ public static class InlineFunctionsDefinitionExtension
double topP = 0,
double presencePenalty = 0,
double frequencyPenalty = 0,
- bool isSensitive = false,
- ITrustService? trustService = null,
IEnumerable? stopSequences = null)
{
var skfunction = kernel.CreateSemanticFunction(
@@ -156,8 +143,6 @@ public static class InlineFunctionsDefinitionExtension
topP,
presencePenalty,
frequencyPenalty,
- isSensitive,
- trustService,
stopSequences);
return skfunction.InvokeAsync();
diff --git a/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs b/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs
index 7bdb35a3b4..f522577cf1 100644
--- a/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs
+++ b/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs
@@ -20,7 +20,6 @@ using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
-using Microsoft.SemanticKernel.Security;
using Microsoft.SemanticKernel.SemanticFunctions;
namespace Microsoft.SemanticKernel.SkillDefinition;
@@ -48,12 +47,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
///
public bool IsSemantic { get; }
- ///
- public bool IsSensitive { get; }
-
- ///
- public ITrustService TrustServiceInstance => this._trustService;
-
///
public CompleteRequestSettings RequestSettings => this._aiRequestSettings;
@@ -68,14 +61,12 @@ public sealed class SKFunction : ISKFunction, IDisposable
/// Signature of the method to invoke
/// Object containing the method to invoke
/// SK skill name
- /// Service used for trust checks, if null the TrustService.DefaultTrusted implementation will be used
/// Application logger
/// SK function instance
public static ISKFunction FromNativeMethod(
MethodInfo method,
object? target = null,
string? skillName = null,
- ITrustService? trustService = null,
ILogger? log = null)
{
if (!method.IsStatic && target is null)
@@ -97,8 +88,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
functionName: methodDetails.Name,
isSemantic: false,
description: methodDetails.Description,
- isSensitive: methodDetails.IsSensitive,
- trustService: trustService,
log: log);
}
@@ -110,8 +99,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
/// SK function name
/// SK function description
/// SK function parameters
- /// Whether the function is set to be sensitive (default false)
- /// Service used for trust checks, if null the TrustService.DefaultTrusted implementation will be used
/// Application logger
/// SK function instance
public static ISKFunction FromNativeFunction(
@@ -120,8 +107,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
string? functionName = null,
string? description = null,
IEnumerable? parameters = null,
- bool isSensitive = false,
- ITrustService? trustService = null,
ILogger? log = null)
{
MethodDetails methodDetails = GetMethodDetails(nativeFunction.Method, nativeFunction.Target, log);
@@ -141,9 +126,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
skillName: skillName!,
functionName: functionName,
isSemantic: false,
- // For native functions, do not read this from the methodDetails
- isSensitive: isSensitive,
- trustService: trustService,
log: log);
}
@@ -153,14 +135,12 @@ public sealed class SKFunction : ISKFunction, IDisposable
/// Name of the skill to which the function to create belongs.
/// Name of the function to create.
/// Semantic function configuration.
- /// Service used for trust checks, if null the TrustService.DefaultTrusted implementation will be used
/// Optional logger for the function.
/// SK function instance.
public static ISKFunction FromSemanticConfig(
string skillName,
string functionName,
SemanticFunctionConfig functionConfig,
- ITrustService? trustService = null,
ILogger? log = null)
{
Verify.NotNull(functionConfig);
@@ -183,8 +163,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
skillName: skillName,
functionName: functionName,
isSemantic: true,
- isSensitive: functionConfig.PromptTemplateConfig.IsSensitive,
- trustService: trustService,
log: log
);
@@ -199,22 +177,12 @@ public sealed class SKFunction : ISKFunction, IDisposable
try
{
string renderedPrompt = await functionConfig.PromptTemplate.RenderAsync(context).ConfigureAwait(false);
-
- // Validates the rendered prompt before executing the completion
- // The prompt template might have function calls that could result in the context becoming untrusted,
- // this way this hook should check again if the context became untrusted
- TrustAwareString prompt = await func.TrustServiceInstance.ValidatePromptAsync(func, context, renderedPrompt).ConfigureAwait(false);
- var completionResults = await client.GetCompletionsAsync(prompt, requestSettings, context.CancellationToken).ConfigureAwait(false);
+ var completionResults = await client.GetCompletionsAsync(renderedPrompt, requestSettings, context.CancellationToken).ConfigureAwait(false);
string completion = await GetCompletionsResultContentAsync(completionResults, context.CancellationToken).ConfigureAwait(false);
// Update the result with the completion
- context.Variables.UpdateKeepingTrustState(completion);
+ context.Variables.Update(completion);
- // Flag the result as untrusted if the prompt has been considered untrusted
- if (!prompt.IsTrusted)
- {
- context.UntrustResult();
- }
context.ModelResults = completionResults.Select(c => c.ModelResult).ToArray();
}
catch (AIException ex)
@@ -260,8 +228,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
// If the function is invoked manually, the user might have left out the skill collection
context.Skills ??= this._skillCollection;
- var validateContextResult = await this.TrustServiceInstance.ValidateContextAsync(this, context).ConfigureAwait(false);
-
if (this.IsSemantic)
{
this.AddDefaultValues(context.Variables);
@@ -283,12 +249,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
}
}
- // If the context has been considered untrusted, make sure the output of the function is also untrusted
- if (!validateContextResult)
- {
- context.UntrustResult();
- }
-
return context;
}
@@ -367,7 +327,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
private IReadOnlySkillCollection? _skillCollection;
private Lazy? _aiService = null;
private CompleteRequestSettings _aiRequestSettings = new();
- private readonly ITrustService _trustService;
private struct MethodDetails
{
@@ -375,7 +334,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
public List Parameters { get; set; }
public string Name { get; set; }
public string Description { get; set; }
- public bool IsSensitive { get; set; }
}
private static async Task GetCompletionsResultContentAsync(IReadOnlyList completions, CancellationToken cancellationToken = default)
@@ -391,8 +349,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
string functionName,
string description,
bool isSemantic = false,
- bool isSensitive = false,
- ITrustService? trustService = null,
ILogger? log = null)
{
Verify.NotNull(delegateFunction);
@@ -402,14 +358,10 @@ public sealed class SKFunction : ISKFunction, IDisposable
this._log = log ?? NullLogger.Instance;
- // If no trust service is specified, use the default implementation
- this._trustService = trustService ?? TrustService.DefaultTrusted;
-
this._function = delegateFunction;
this.Parameters = parameters;
this.IsSemantic = isSemantic;
- this.IsSensitive = isSensitive;
this.Name = functionName;
this.SkillName = skillName;
this.Description = description;
@@ -464,7 +416,6 @@ public sealed class SKFunction : ISKFunction, IDisposable
{
Name = functionName!,
Description = description ?? string.Empty,
- IsSensitive = functionAttribute?.IsSensitive ?? false,
};
(result.Function, result.Parameters) = GetDelegateInfo(target, method);
@@ -612,7 +563,7 @@ public sealed class SKFunction : ISKFunction, IDisposable
method.GetCustomAttribute(inherit: true) is SKFunctionInputAttribute inputAttr)
{
sawFirstParameter = true;
- return (static (SKContext ctx) => ctx.Variables.Input.Value, inputAttr.ToParameterView());
+ return (static (SKContext ctx) => ctx.Variables.Input, inputAttr.ToParameterView());
}
// Use either the parameter's optional default value as contained in parameter metadata (e.g. `string s = "hello"`)
@@ -655,7 +606,7 @@ public sealed class SKFunction : ISKFunction, IDisposable
Func parameterFunc = (SKContext ctx) =>
{
// 1. Use the value of the variable if it exists.
- if (ctx.Variables.Get(name, out string value))
+ if (ctx.Variables.TryGetValue(name, out string? value))
{
return Process(value);
}
@@ -669,7 +620,7 @@ public sealed class SKFunction : ISKFunction, IDisposable
// 3. Otherwise, use "input" if this is the first (or only) parameter.
if (fallBackToInput)
{
- return Process(ctx.Variables.Input.Value);
+ return Process(ctx.Variables.Input);
}
// 4. Otherwise, fail.
@@ -763,7 +714,7 @@ public sealed class SKFunction : ISKFunction, IDisposable
{
return static (result, context) =>
{
- context.Variables.UpdateKeepingTrustState((string?)result);
+ context.Variables.Update((string?)result);
return Task.FromResult(context);
};
}
@@ -772,7 +723,7 @@ public sealed class SKFunction : ISKFunction, IDisposable
{
return async static (result, context) =>
{
- context.Variables.UpdateKeepingTrustState(await ((Task)ThrowIfNullResult(result)).ConfigureAwait(false));
+ context.Variables.Update(await ((Task)ThrowIfNullResult(result)).ConfigureAwait(false));
return context;
};
}
@@ -781,7 +732,7 @@ public sealed class SKFunction : ISKFunction, IDisposable
{
return async static (result, context) =>
{
- context.Variables.UpdateKeepingTrustState(await ((ValueTask)ThrowIfNullResult(result)).ConfigureAwait(false));
+ context.Variables.Update(await ((ValueTask)ThrowIfNullResult(result)).ConfigureAwait(false));
return context;
};
}
@@ -797,7 +748,7 @@ public sealed class SKFunction : ISKFunction, IDisposable
return (result, context) =>
{
- context.Variables.UpdateKeepingTrustState(formatter(result, context.Culture));
+ context.Variables.Update(formatter(result, context.Culture));
return Task.FromResult(context);
};
}
@@ -813,7 +764,7 @@ public sealed class SKFunction : ISKFunction, IDisposable
return async (result, context) =>
{
await ((Task)ThrowIfNullResult(result)).ConfigureAwait(false);
- context.Variables.UpdateKeepingTrustState(taskResultFormatter(taskResultGetter.Invoke(result!, Array.Empty
/// The SKContext.
- [SKFunction("Extract user intent")]
- [SKFunctionName("ExtractUserIntent")]
- [SKFunctionContextParameter(Name = "chatId", Description = "Chat ID to extract history from")]
- [SKFunctionContextParameter(Name = "audience", Description = "The audience the chat bot is interacting with.")]
+ [SKFunction, Description("Extract user intent")]
+ [SKParameter("chatId", "Chat ID to extract history from")]
+ [SKParameter("audience", "The audience the chat bot is interacting with.")]
public async Task ExtractUserIntentAsync(SKContext context)
{
var tokenLimit = this._promptOptions.CompletionTokenLimit;
@@ -140,9 +140,8 @@ public class ChatSkill
/// Extract the list of participants from the conversation history.
/// Note that only those who have spoken will be included.
///
- [SKFunction("Extract audience list")]
- [SKFunctionName("ExtractAudience")]
- [SKFunctionContextParameter(Name = "chatId", Description = "Chat ID to extract history from")]
+ [SKFunction, Description("Extract audience list")]
+ [SKParameter("chatId", "Chat ID to extract history from")]
public async Task ExtractAudienceAsync(SKContext context)
{
var tokenLimit = this._promptOptions.CompletionTokenLimit;
@@ -184,15 +183,11 @@ public class ChatSkill
/// Extract chat history.
///
/// Contains the 'tokenLimit' controlling the length of the prompt.
- [SKFunction("Extract chat history")]
- [SKFunctionName("ExtractChatHistory")]
- [SKFunctionContextParameter(Name = "chatId", Description = "Chat ID to extract history from")]
- [SKFunctionContextParameter(Name = "tokenLimit", Description = "Maximum number of tokens")]
- public async Task ExtractChatHistoryAsync(SKContext context)
+ [SKFunction, Description("Extract chat history")]
+ public async Task ExtractChatHistoryAsync(
+ [Description("Chat ID to extract history from")] string chatId,
+ [Description("Maximum number of tokens")] int tokenLimit)
{
- var chatId = context["chatId"];
- var tokenLimit = int.Parse(context["tokenLimit"], new NumberFormatInfo());
-
var messages = await this._chatMessageRepository.FindByChatIdAsync(chatId);
var sortedMessages = messages.OrderByDescending(m => m.Timestamp);
@@ -242,36 +237,19 @@ public class ChatSkill
/// messages to memory, and fill in the necessary context variables for completing the
/// prompt that will be rendered by the template engine.
///
- ///
- /// Contains the 'tokenLimit' and the 'contextTokenLimit' controlling the length of the prompt.
- [SKFunction("Get chat response")]
- [SKFunctionName("Chat")]
- [SKFunctionInput(Description = "The new message")]
- [SKFunctionContextParameter(Name = "userId", Description = "Unique and persistent identifier for the user")]
- [SKFunctionContextParameter(Name = "userName", Description = "Name of the user")]
- [SKFunctionContextParameter(Name = "chatId", Description = "Unique and persistent identifier for the chat")]
- [SKFunctionContextParameter(Name = "proposedPlan", Description = "Previously proposed plan that is approved")]
- [SKFunctionContextParameter(Name = "messageType", Description = "Type of the message")]
- [SKFunctionContextParameter(Name = "responseMessageId", Description = "ID of the response message for planner")]
- public async Task ChatAsync(string message, SKContext context)
+ [SKFunction, Description("Get chat response")]
+ public async Task ChatAsync(
+ [Description("The new message")] string message,
+ [Description("Unique and persistent identifier for the user")] string userId,
+ [Description("Name of the user")] string userName,
+ [Description("Unique and persistent identifier for the chat")] string chatId,
+ [Description("Type of the message")] string messageType,
+ [Description("Previously proposed plan that is approved"), DefaultValue(null), SKName("proposedPlan")] string? planJson,
+ [Description("ID of the response message for planner"), DefaultValue(null), SKName("responseMessageId")] string? messageId,
+ SKContext context)
{
- // TODO: check if user has access to the chat
- var userId = context["userId"];
- var userName = context["userName"];
- var chatId = context["chatId"];
- var messageType = context["messageType"];
-
// Save this new message to memory such that subsequent chat responses can use it
- try
- {
- await this.SaveNewMessageAsync(message, userId, userName, chatId, messageType);
- }
- catch (Exception ex) when (!ex.IsCriticalException())
- {
- context.Log.LogError("Unable to save new message: {0}", ex.Message);
- context.Fail($"Unable to save new message: {ex.Message}", ex);
- return context;
- }
+ await this.SaveNewMessageAsync(message, userId, userName, chatId, messageType);
// Clone the context to avoid modifying the original context variables.
var chatContext = Utilities.CopyContextWithVariablesClone(context);
@@ -280,16 +258,15 @@ public class ChatSkill
// Check if plan exists in ask's context variables.
// If plan was returned at this point, that means it was approved or cancelled.
// Update the response previously saved in chat history with state
- if (context.Variables.TryGetValue("proposedPlan", out string? planJson)
- && !string.IsNullOrWhiteSpace(planJson)
- && context.Variables.TryGetValue("responseMessageId", out string? messageId))
+ if (!string.IsNullOrWhiteSpace(planJson) &&
+ !string.IsNullOrEmpty(messageId))
{
await this.UpdateResponseAsync(planJson, messageId);
}
var response = chatContext.Variables.ContainsKey("userCancelledPlan")
? "I am sorry the plan did not meet your goals."
- : await this.GetChatResponseAsync(chatContext);
+ : await this.GetChatResponseAsync(chatId, chatContext);
if (chatContext.ErrorOccurred)
{
@@ -299,24 +276,14 @@ public class ChatSkill
// Retrieve the prompt used to generate the response
// and return it to the caller via the context variables.
- var prompt = chatContext.Variables.ContainsKey("prompt")
- ? chatContext.Variables["prompt"]
- : string.Empty;
+ chatContext.Variables.TryGetValue("prompt", out string? prompt);
+ prompt ??= string.Empty;
context.Variables.Set("prompt", prompt);
// Save this response to memory such that subsequent chat responses can use it
- try
- {
- ChatMessage botMessage = await this.SaveNewResponseAsync(response, prompt, chatId);
- context.Variables.Set("messageId", botMessage.Id);
- context.Variables.Set("messageType", ((int)botMessage.Type).ToString(CultureInfo.InvariantCulture));
- }
- catch (Exception ex) when (!ex.IsCriticalException())
- {
- context.Log.LogError("Unable to save new response: {0}", ex.Message);
- context.Fail($"Unable to save new response: {ex.Message}");
- return context;
- }
+ ChatMessage botMessage = await this.SaveNewResponseAsync(response, prompt, chatId);
+ context.Variables.Set("messageId", botMessage.Id);
+ context.Variables.Set("messageType", ((int)botMessage.Type).ToString(CultureInfo.InvariantCulture));
// Extract semantic chat memory
await SemanticChatMemoryExtractor.ExtractSemanticChatMemoryAsync(
@@ -336,7 +303,7 @@ public class ChatSkill
///
/// The SKContext.
/// A response from the model.
- private async Task GetChatResponseAsync(SKContext chatContext)
+ private async Task GetChatResponseAsync(string chatId, SKContext chatContext)
{
// 0. Get the audience
var audience = await this.GetAudienceAsync(chatContext);
@@ -371,7 +338,7 @@ public class ChatSkill
// 4. Query relevant semantic memories
var chatMemoriesTokenLimit = (int)(remainingToken * this._promptOptions.MemoriesResponseContextWeight);
- var chatMemories = await this.QueryChatMemoriesAsync(chatContext, userIntent, chatMemoriesTokenLimit);
+ var chatMemories = await this._semanticChatMemorySkill.QueryMemoriesAsync(userIntent, chatId, chatMemoriesTokenLimit, chatContext.Memory);
if (chatContext.ErrorOccurred)
{
return string.Empty;
@@ -379,7 +346,7 @@ public class ChatSkill
// 5. Query relevant document memories
var documentContextTokenLimit = (int)(remainingToken * this._promptOptions.DocumentContextWeight);
- var documentMemories = await this.QueryDocumentsAsync(chatContext, userIntent, documentContextTokenLimit);
+ var documentMemories = await this._documentMemorySkill.QueryDocumentsAsync(userIntent, chatId, documentContextTokenLimit, chatContext.Memory);
if (chatContext.ErrorOccurred)
{
return string.Empty;
@@ -391,7 +358,7 @@ public class ChatSkill
var chatContextTextTokenCount = remainingToken - Utilities.TokenCount(chatContextText);
if (chatContextTextTokenCount > 0)
{
- var chatHistory = await this.GetChatHistoryAsync(chatContext, chatContextTextTokenCount);
+ var chatHistory = await this.ExtractChatHistoryAsync(chatId, chatContextTextTokenCount);
if (chatContext.ErrorOccurred)
{
return string.Empty;
@@ -490,62 +457,13 @@ public class ChatSkill
return userIntent;
}
- ///
- /// Helper function create the correct context variables to
- /// extract chat history messages from the conversation history.
- ///
- private Task GetChatHistoryAsync(SKContext context, int tokenLimit)
- {
- var contextVariables = new ContextVariables();
- contextVariables.Set("chatId", context["chatId"]);
- contextVariables.Set("tokenLimit", tokenLimit.ToString(new NumberFormatInfo()));
-
- var chatHistoryContext = new SKContext(
- contextVariables,
- context.Memory,
- context.Skills,
- context.Log,
- context.CancellationToken
- );
-
- var chatHistory = this.ExtractChatHistoryAsync(chatHistoryContext);
-
- // Propagate the error
- if (chatHistoryContext.ErrorOccurred)
- {
- context.Fail(chatHistoryContext.LastErrorDescription);
- }
-
- return chatHistory;
- }
-
///
/// Helper function create the correct context variables to
/// query chat memories from the chat memory store.
///
private Task QueryChatMemoriesAsync(SKContext context, string userIntent, int tokenLimit)
{
- var contextVariables = new ContextVariables();
- contextVariables.Set("chatId", context["chatId"]);
- contextVariables.Set("tokenLimit", tokenLimit.ToString(new NumberFormatInfo()));
-
- var chatMemoriesContext = new SKContext(
- contextVariables,
- context.Memory,
- context.Skills,
- context.Log,
- context.CancellationToken
- );
-
- var chatMemories = this._semanticChatMemorySkill.QueryMemoriesAsync(userIntent, chatMemoriesContext);
-
- // Propagate the error
- if (chatMemoriesContext.ErrorOccurred)
- {
- context.Fail(chatMemoriesContext.LastErrorDescription);
- }
-
- return chatMemories;
+ return this._semanticChatMemorySkill.QueryMemoriesAsync(userIntent, context["chatId"], tokenLimit, context.Memory);
}
///
@@ -554,27 +472,7 @@ public class ChatSkill
///
private Task QueryDocumentsAsync(SKContext context, string userIntent, int tokenLimit)
{
- var contextVariables = new ContextVariables();
- contextVariables.Set("chatId", context["chatId"]);
- contextVariables.Set("tokenLimit", tokenLimit.ToString(new NumberFormatInfo()));
-
- var documentMemoriesContext = new SKContext(
- contextVariables,
- context.Memory,
- context.Skills,
- context.Log,
- context.CancellationToken
- );
-
- var documentMemories = this._documentMemorySkill.QueryDocumentsAsync(userIntent, documentMemoriesContext);
-
- // Propagate the error
- if (documentMemoriesContext.ErrorOccurred)
- {
- context.Fail(documentMemoriesContext.LastErrorDescription);
- }
-
- return documentMemories;
+ return this._documentMemorySkill.QueryDocumentsAsync(userIntent, context["chatId"], tokenLimit, context.Memory);
}
///
diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/DocumentMemorySkill.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/DocumentMemorySkill.cs
index 01475eedbc..ac9f879380 100644
--- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/DocumentMemorySkill.cs
+++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/DocumentMemorySkill.cs
@@ -1,12 +1,11 @@
īģŋ// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
-using System.Globalization;
+using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.SemanticKernel.Memory;
-using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using SemanticKernel.Service.CopilotChat.Options;
@@ -43,15 +42,13 @@ public class DocumentMemorySkill
///
/// Query to match.
/// The SkContext.
- [SKFunction("Query documents in the memory given a user message")]
- [SKFunctionName("QueryDocuments")]
- [SKFunctionInput(Description = "Query to match.")]
- [SKFunctionContextParameter(Name = "chatId", Description = "ID of the chat that owns the documents")]
- [SKFunctionContextParameter(Name = "tokenLimit", Description = "Maximum number of tokens")]
- public async Task QueryDocumentsAsync(string query, SKContext context)
+ [SKFunction, Description("Query documents in the memory given a user message")]
+ public async Task QueryDocumentsAsync(
+ [Description("Query to match.")] string query,
+ [Description("ID of the chat that owns the documents")] string chatId,
+ [Description("Maximum number of tokens")] int tokenLimit,
+ ISemanticTextMemory textMemory)
{
- string chatId = context.Variables["chatId"];
- int tokenLimit = int.Parse(context.Variables["tokenLimit"], new NumberFormatInfo());
var remainingToken = tokenLimit;
// Search for relevant document snippets.
@@ -64,7 +61,7 @@ public class DocumentMemorySkill
List relevantMemories = new();
foreach (var documentCollection in documentCollections)
{
- var results = context.Memory.SearchAsync(
+ var results = textMemory.SearchAsync(
documentCollection,
query,
limit: 100,
diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ExternalInformationSkill.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ExternalInformationSkill.cs
index ccad3351c3..9fea94f622 100644
--- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ExternalInformationSkill.cs
+++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ExternalInformationSkill.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text.Json;
@@ -64,12 +65,12 @@ public class ExternalInformationSkill
///
/// Extract relevant additional knowledge using a planner.
///
- [SKFunction("Acquire external information")]
- [SKFunctionName("AcquireExternalInformation")]
- [SKFunctionInput(Description = "The intent to whether external information is needed")]
- [SKFunctionContextParameter(Name = "tokenLimit", Description = "Maximum number of tokens")]
- [SKFunctionContextParameter(Name = "proposedPlan", Description = "Previously proposed plan that is approved")]
- public async Task AcquireExternalInformationAsync(string userIntent, SKContext context)
+ [SKFunction, Description("Acquire external information")]
+ [SKParameter("tokenLimit", "Maximum number of tokens")]
+ [SKParameter("proposedPlan", "Previously proposed plan that is approved")]
+ public async Task AcquireExternalInformationAsync(
+ [Description("The intent to whether external information is needed")] string userIntent,
+ SKContext context)
{
FunctionsView functions = this._planner.Kernel.Skills.GetFunctionsView(true, true);
if (functions.NativeFunctions.IsEmpty && functions.SemanticFunctions.IsEmpty)
@@ -125,13 +126,13 @@ public class ExternalInformationSkill
if (plan.Steps.Count > 0)
{
- // Parameters stored in plan's top level state
- this.MergeContextIntoPlan(context.Variables, plan.State);
+ // Parameters stored in plan's top level
+ this.MergeContextIntoPlan(context.Variables, plan.Parameters);
// TODO: Improve Kernel to give developers option to skip this override
// (i.e., keep functions regardless of whether they're available in the planner's context or not)
Plan sanitizedPlan = this.SanitizePlan(plan, context);
- sanitizedPlan.State.Update(plan.State);
+ sanitizedPlan.Parameters.Update(plan.Parameters);
this.ProposedPlan = new ProposedPlan(sanitizedPlan, this._planner.PlannerOptions!.Type, PlanState.NoOp);
}
diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/SemanticChatMemorySkill.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/SemanticChatMemorySkill.cs
index 6358a0cb18..b9d7efaeb1 100644
--- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/SemanticChatMemorySkill.cs
+++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/SemanticChatMemorySkill.cs
@@ -1,12 +1,11 @@
īģŋ// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
-using System.Globalization;
+using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.SemanticKernel.Memory;
-using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using SemanticKernel.Service.CopilotChat.Options;
@@ -37,22 +36,20 @@ public class SemanticChatMemorySkill
/// Query to match.
/// The SKContext
/// A string containing the relevant memories.
- [SKFunction("Query chat memories")]
- [SKFunctionName("QueryMemories")]
- [SKFunctionInput(Description = "Query to match.")]
- [SKFunctionContextParameter(Name = "chatId", Description = "Chat ID to query history from")]
- [SKFunctionContextParameter(Name = "tokenLimit", Description = "Maximum number of tokens")]
- public async Task QueryMemoriesAsync(string query, SKContext context)
+ [SKFunction, Description("Query chat memories")]
+ public async Task QueryMemoriesAsync(
+ [Description("Query to match.")] string query,
+ [Description("Chat ID to query history from")] string chatId,
+ [Description("Maximum number of tokens")] int tokenLimit,
+ ISemanticTextMemory textMemory)
{
- var chatId = context["chatId"];
- var tokenLimit = int.Parse(context["tokenLimit"], new NumberFormatInfo());
var remainingToken = tokenLimit;
// Search for relevant memories.
List relevantMemories = new();
foreach (var memoryName in this._promptOptions.MemoryMap.Keys)
{
- var results = context.Memory.SearchAsync(
+ var results = textMemory.SearchAsync(
SemanticChatMemoryExtractor.MemoryCollectionName(chatId, memoryName),
query,
limit: 100,
diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChatWebApi.csproj b/samples/apps/copilot-chat-app/webapi/CopilotChatWebApi.csproj
index dea8e5e98e..95f4dd6f79 100644
--- a/samples/apps/copilot-chat-app/webapi/CopilotChatWebApi.csproj
+++ b/samples/apps/copilot-chat-app/webapi/CopilotChatWebApi.csproj
@@ -11,13 +11,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/samples/apps/copilot-chat-app/webapi/Options/ServiceOptions.cs b/samples/apps/copilot-chat-app/webapi/Options/ServiceOptions.cs
index 58c0554729..31f7d6d81e 100644
--- a/samples/apps/copilot-chat-app/webapi/Options/ServiceOptions.cs
+++ b/samples/apps/copilot-chat-app/webapi/Options/ServiceOptions.cs
@@ -1,6 +1,5 @@
īģŋ// Copyright (c) Microsoft. All rights reserved.
-using System;
using System.ComponentModel.DataAnnotations;
namespace SemanticKernel.Service.Options;
@@ -16,7 +15,7 @@ public class ServiceOptions
/// Configuration Key Vault URI
///
[Url]
- public Uri? KeyVaultUri { get; set; }
+ public string? KeyVault { get; set; }
///
/// Local directory in which to load semantic skills.
diff --git a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs
index 703ba93743..5432bccfde 100644
--- a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs
+++ b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs
@@ -12,8 +12,8 @@ using Microsoft.SemanticKernel.AI.Embeddings;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding;
using Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch;
using Microsoft.SemanticKernel.Connectors.Memory.Qdrant;
-using Microsoft.SemanticKernel.CoreSkills;
using Microsoft.SemanticKernel.Memory;
+using Microsoft.SemanticKernel.Skills.Core;
using Microsoft.SemanticKernel.TemplateEngine;
using SemanticKernel.Service.CopilotChat.Extensions;
using SemanticKernel.Service.CopilotChat.Storage;
diff --git a/samples/apps/copilot-chat-app/webapi/appsettings.json b/samples/apps/copilot-chat-app/webapi/appsettings.json
index a48432da49..224678239f 100644
--- a/samples/apps/copilot-chat-app/webapi/appsettings.json
+++ b/samples/apps/copilot-chat-app/webapi/appsettings.json
@@ -17,7 +17,7 @@
//
"Service": {
// "SemanticSkillsDirectory": "",
- // "KeyVaultUri": ""
+ // "KeyVault": ""
},
//
diff --git a/samples/apps/copilot-chat-app/webapi/nuget.config b/samples/apps/copilot-chat-app/webapi/nuget.config
new file mode 100644
index 0000000000..7159fcd04c
--- /dev/null
+++ b/samples/apps/copilot-chat-app/webapi/nuget.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/apps/copilot-chat-app/webapp/.env.example b/samples/apps/copilot-chat-app/webapp/.env.example
index 695637247d..fea9742a82 100644
--- a/samples/apps/copilot-chat-app/webapp/.env.example
+++ b/samples/apps/copilot-chat-app/webapp/.env.example
@@ -15,5 +15,15 @@ REACT_APP_SK_API_KEY=
# SSL_KEY_FILE=local-cert.key
# For CI and testing purposes only
-REACT_APP_TEST_USER_ACCOUNT=
-REACT_APP_TEST_USER_PASSWORD=
\ No newline at end of file
+REACT_APP_TEST_USER_ACCOUNT1=
+REACT_APP_TEST_USER_ACCOUNT1_INITIALS=
+REACT_APP_TEST_USER_ACCOUNT2=
+REACT_APP_TEST_USER_PASSWORD=
+
+REACT_APP_TEST_JIRA_EMAIL=
+REACT_APP_TEST_JIRA_ACCESS_TOKEN=
+REACT_APP_TEST_JIRA_SERVER_URL=
+
+REACT_APP_TEST_GITHUB_ACCESS_TOKEN=
+REACT_APP_TEST_GITHUB_ACCOUNT_OWNER=
+REACT_APP_TEST_GITHUB_REPOSITORY_NAME=
\ No newline at end of file
diff --git a/samples/apps/copilot-chat-app/webapp/package.json b/samples/apps/copilot-chat-app/webapp/package.json
index f6fd8dc07e..80d1162b4b 100644
--- a/samples/apps/copilot-chat-app/webapp/package.json
+++ b/samples/apps/copilot-chat-app/webapp/package.json
@@ -24,7 +24,7 @@
"@fluentui/react-icons": "^2.0.193",
"@fluentui/react-northstar": "^0.66.4",
"@microsoft/signalr": "^7.0.5",
- "@playwright/test": "^1.34.3",
+ "@playwright/test": "^1.35.1",
"@reduxjs/toolkit": "^1.9.1",
"debug": "^4.3.4",
"microsoft-cognitiveservices-speech-sdk": "^1.27.0",
diff --git a/samples/apps/copilot-chat-app/webapp/playwright.config.ts b/samples/apps/copilot-chat-app/webapp/playwright.config.ts
index 9cebb3418a..b90ba2fd4d 100644
--- a/samples/apps/copilot-chat-app/webapp/playwright.config.ts
+++ b/samples/apps/copilot-chat-app/webapp/playwright.config.ts
@@ -23,7 +23,7 @@ export default defineConfig({
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
- retries: process.env.CI ? 2 : 0,
+ retries: process.env.CI ? 3 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
diff --git a/samples/apps/copilot-chat-app/webapp/src/App.tsx b/samples/apps/copilot-chat-app/webapp/src/App.tsx
index e1fbb32688..b15b9f37b8 100644
--- a/samples/apps/copilot-chat-app/webapp/src/App.tsx
+++ b/samples/apps/copilot-chat-app/webapp/src/App.tsx
@@ -2,8 +2,7 @@
import { AuthenticatedTemplate, UnauthenticatedTemplate, useIsAuthenticated, useMsal } from '@azure/msal-react';
import { Subtitle1, makeStyles, shorthands, tokens } from '@fluentui/react-components';
-import { Alert } from '@fluentui/react-components/unstable';
-import { Dismiss16Regular } from '@fluentui/react-icons';
+
import * as React from 'react';
import { FC, useEffect } from 'react';
import { UserSettings } from './components/header/UserSettings';
@@ -12,40 +11,39 @@ import BackendProbe from './components/views/BackendProbe';
import { ChatView } from './components/views/ChatView';
import Loading from './components/views/Loading';
import { Login } from './components/views/Login';
+import { AlertType } from './libs/models/AlertType';
import { useChat } from './libs/useChat';
import { useAppDispatch, useAppSelector } from './redux/app/hooks';
import { RootState } from './redux/app/store';
-import { removeAlert } from './redux/features/app/appSlice';
-import { setLoggedInUserId } from './redux/features/conversations/conversationsSlice';
-import { CopilotChatTokens } from './styles';
+import { addAlert, setActiveUserInfo } from './redux/features/app/appSlice';
export const useClasses = makeStyles({
container: {
- ...shorthands.overflow('hidden'),
display: 'flex',
flexDirection: 'column',
- width: '100%',
height: '100vh',
+ width: '100%',
+ ...shorthands.overflow('hidden'),
},
header: {
- backgroundColor: CopilotChatTokens.backgroundColor,
- width: '100%',
- height: '48px',
- color: '#FFF',
+ alignItems: 'center',
+ backgroundColor: tokens.colorBrandForeground2,
+ color: tokens.colorNeutralForegroundOnBrand,
display: 'flex',
'& h1': {
- paddingLeft: '20px',
+ paddingLeft: tokens.spacingHorizontalXL,
display: 'flex',
},
- alignItems: 'center',
+ height: '48px',
justifyContent: 'space-between',
+ width: '100%',
},
persona: {
- marginRight: '20px',
+ marginRight: tokens.spacingHorizontalXXL,
},
cornerItems: {
display: 'flex',
- ...shorthands.gap(tokens.spacingHorizontalXS),
+ ...shorthands.gap(tokens.spacingHorizontalS),
},
});
@@ -60,18 +58,32 @@ const App: FC = () => {
const classes = useClasses();
const [appState, setAppState] = React.useState(AppState.ProbeForBackend);
- const { alerts } = useAppSelector((state: RootState) => state.app);
const dispatch = useAppDispatch();
const { instance, inProgress } = useMsal();
- const account = instance.getActiveAccount();
+ const { activeUserInfo } = useAppSelector((state: RootState) => state.app);
const isAuthenticated = useIsAuthenticated();
const chat = useChat();
useEffect(() => {
- if (isAuthenticated && account) {
- dispatch(setLoggedInUserId(account.homeAccountId));
+ if (isAuthenticated) {
+ let isActiveUserInfoSet = activeUserInfo !== undefined;
+ if (!isActiveUserInfoSet) {
+ const account = instance.getActiveAccount();
+ if (!account) {
+ dispatch(addAlert({ type: AlertType.Error, message: 'Unable to get active logged in account.' }));
+ } else {
+ dispatch(
+ setActiveUserInfo({
+ id: account.homeAccountId,
+ email: account.username, // username in an AccountInfo object is the email address
+ username: account.name ?? account.username,
+ }),
+ );
+ }
+ isActiveUserInfoSet = true;
+ }
if (appState === AppState.LoadingChats) {
// Load all chats from memory
@@ -82,13 +94,10 @@ const App: FC = () => {
});
}
}
+
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [instance, inProgress, isAuthenticated, appState]);
- const onDismissAlert = (index: number) => {
- dispatch(removeAlert(index));
- };
-
// TODO: handle error case of missing account information
return (
@@ -114,27 +123,6 @@ const App: FC = () => {
/>
- {alerts.map(({ type, message }, index) => {
- return (
- {
- onDismissAlert(index);
- }}
- color="black"
- />
- ),
- }}
- key={`${index}-${type}`}
- >
- {message}
-
- );
- })}
{appState === AppState.ProbeForBackend && (
= ({ isDraggingOver, onDragLeave, onSubmit }) => {
const classes = useClasses();
const { instance, inProgress } = useMsal();
- const account = instance.getActiveAccount();
const chat = useChat();
const dispatch = useAppDispatch();
const [value, setValue] = React.useState('');
@@ -85,6 +83,7 @@ export const ChatInput: React.FC = ({ isDraggingOver, onDragLeav
const [documentImporting, setDocumentImporting] = React.useState(false);
const documentFileRef = useRef(null);
const { conversations, selectedId } = useAppSelector((state: RootState) => state.conversations);
+ const { activeUserInfo } = useAppSelector((state: RootState) => state.app);
React.useEffect(() => {
async function initSpeechRecognizer() {
@@ -189,7 +188,7 @@ export const ChatInput: React.FC = ({ isDraggingOver, onDragLeav
}
// User is considered typing if the input is in focus
dispatch(
- updateUserIsTyping({ userId: account?.homeAccountId, chatId: selectedId, isTyping: true }),
+ updateUserIsTyping({ userId: activeUserInfo?.id, chatId: selectedId, isTyping: true }),
);
}}
onChange={(_event, data) => {
@@ -209,14 +208,14 @@ export const ChatInput: React.FC = ({ isDraggingOver, onDragLeav
onBlur={() => {
// User is considered not typing if the input is not in focus
dispatch(
- updateUserIsTyping({ userId: account?.homeAccountId, chatId: selectedId, isTyping: false }),
+ updateUserIsTyping({ userId: activeUserInfo?.id, chatId: selectedId, isTyping: false }),
);
}}
/>
- {/* Hidden input for file upload. Only accept .txt files for now. */}
+ {/* Hidden input for file upload. Only accept .txt and .pdf files for now. */}
{
const { conversations, selectedId } = useAppSelector((state: RootState) => state.conversations);
+ const { activeUserInfo } = useAppSelector((state: RootState) => state.app);
+
const messages = conversations[selectedId].messages;
const classes = useClasses();
- const { instance } = useMsal();
- const account = instance.getActiveAccount();
-
const dispatch = useAppDispatch();
const scrollViewTargetRef = React.useRef
(null);
const scrollTargetRef = React.useRef(null);
@@ -88,17 +86,13 @@ export const ChatRoom: React.FC = () => {
};
}, []);
- if (!account) {
- return null;
- }
-
const handleSubmit = async (options: GetResponseOptions) => {
log('submitting user chat message');
const chatInput: IChatMessage = {
timestamp: new Date().getTime(),
- userId: account.homeAccountId,
- userName: account.name ?? account.username,
+ userId: activeUserInfo?.id as string,
+ userName: activeUserInfo?.username as string,
content: options.value,
type: options.messageType,
authorRole: AuthorRoles.User,
diff --git a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatStatus.tsx b/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatStatus.tsx
index f186a31a3b..8cd3971d8d 100644
--- a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatStatus.tsx
+++ b/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatStatus.tsx
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
-import { useMsal } from '@azure/msal-react';
import React from 'react';
import { IChatUser } from '../../libs/models/ChatUser';
import { useAppSelector } from '../../redux/app/hooks';
@@ -8,24 +7,23 @@ import { RootState } from '../../redux/app/store';
import { TypingIndicatorRenderer } from './typing-indicator/TypingIndicatorRenderer';
export const ChatStatus: React.FC = () => {
- const { instance } = useMsal();
- const account = instance.getActiveAccount();
const { conversations, selectedId } = useAppSelector((state: RootState) => state.conversations);
const { users } = conversations[selectedId];
+ const { activeUserInfo } = useAppSelector((state: RootState) => state.app);
const [typingUserList, setTypingUserList] = React.useState([]);
React.useEffect(() => {
const checkAreTyping = () => {
const updatedTypingUsers: IChatUser[] = users.filter(
(chatUser: IChatUser) =>
- chatUser.id !== account?.homeAccountId &&
+ chatUser.id !== activeUserInfo?.id &&
chatUser.isTyping,
);
setTypingUserList(updatedTypingUsers);
};
checkAreTyping();
- }, [account?.homeAccountId, users]);
+ }, [activeUserInfo, users]);
return (
diff --git a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatWindow.tsx b/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatWindow.tsx
index dbf61ce85b..91279b30a0 100644
--- a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatWindow.tsx
+++ b/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatWindow.tsx
@@ -9,6 +9,7 @@ import {
InputOnChangeData,
Label,
makeStyles,
+ mergeClasses,
Persona,
Popover,
PopoverSurface,
@@ -21,14 +22,15 @@ import {
tokens,
Tooltip,
} from '@fluentui/react-components';
-import { Edit24Filled, EditRegular } from '@fluentui/react-icons';
+import { Alert } from '@fluentui/react-components/unstable';
+import { Dismiss16Regular, Edit24Filled, EditRegular } from '@fluentui/react-icons';
import React, { useEffect, useState } from 'react';
import { AuthHelper } from '../../libs/auth/AuthHelper';
import { AlertType } from '../../libs/models/AlertType';
import { ChatService } from '../../libs/services/ChatService';
import { useAppDispatch, useAppSelector } from '../../redux/app/hooks';
import { RootState } from '../../redux/app/store';
-import { addAlert } from '../../redux/features/app/appSlice';
+import { addAlert, removeAlert } from '../../redux/features/app/appSlice';
import { editConversationTitle } from '../../redux/features/conversations/conversationsSlice';
import { ChatResourceList } from './ChatResourceList';
import { ChatRoom } from './ChatRoom';
@@ -79,6 +81,18 @@ const useClasses = makeStyles({
input: {
width: '100%',
},
+ alert: {
+ ...shorthands.borderRadius(0),
+ },
+ infoAlert: {
+ fontWeight: tokens.fontWeightRegular,
+ color: tokens.colorNeutralForeground1,
+ backgroundColor: tokens.colorNeutralBackground6,
+ ...shorthands.borderRadius(0),
+ fontSize: tokens.fontSizeBase200,
+ lineHeight: tokens.lineHeightBase200,
+ ...shorthands.borderBottom(tokens.strokeWidthThin, 'solid', tokens.colorNeutralStroke1),
+ },
});
export const ChatWindow: React.FC = () => {
@@ -89,6 +103,7 @@ export const ChatWindow: React.FC = () => {
const [title = '', setTitle] = useState(selectedId);
const [isEditing, setIsEditing] = useState(false);
const { instance, inProgress } = useMsal();
+ const { alerts } = useAppSelector((state: RootState) => state.app);
const chatService = new ChatService(process.env.REACT_APP_BACKEND_URI as string);
@@ -110,6 +125,10 @@ export const ChatWindow: React.FC = () => {
setSelectedTab(data.value);
};
+ const onDismissAlert = (index: number) => {
+ dispatch(removeAlert(index));
+ };
+
const onClose = () => {
setTitle(chatName);
setIsEditing(!isEditing);
@@ -138,6 +157,28 @@ export const ChatWindow: React.FC = () => {
return (
+ {alerts.map(({ type, message }, index) => {
+ return (
+
{
+ onDismissAlert(index);
+ }}
+ color="black"
+ />
+ ),
+ }}
+ key={`${index}-${type}`}
+ className={mergeClasses(classes.alert, classes.infoAlert)}
+ >
+ {message}
+
+ );
+ })}
{
: }
appearance="transparent"
onClick={onClose}
@@ -173,10 +215,10 @@ export const ChatWindow: React.FC = () => {
-
+
Chat
-
+
Files
diff --git a/samples/apps/copilot-chat-app/webapp/src/components/chat/ShareBotMenu.tsx b/samples/apps/copilot-chat-app/webapp/src/components/chat/ShareBotMenu.tsx
index 35f02283e4..e891ddcca0 100644
--- a/samples/apps/copilot-chat-app/webapp/src/components/chat/ShareBotMenu.tsx
+++ b/samples/apps/copilot-chat-app/webapp/src/components/chat/ShareBotMenu.tsx
@@ -34,20 +34,15 @@ export const ShareBotMenu: FC = ({ chatId, chatTitle }) => {