diff --git a/packages/fx-core/src/component/generator/templates/metadata/teams.ts b/packages/fx-core/src/component/generator/templates/metadata/teams.ts
index 746ab37d7c0..2d451268e50 100644
--- a/packages/fx-core/src/component/generator/templates/metadata/teams.ts
+++ b/packages/fx-core/src/component/generator/templates/metadata/teams.ts
@@ -47,6 +47,12 @@ const chatWithYourDataTemplates: Template[] = [
language: "csharp",
description: "",
},
+ {
+ id: "custom-copilot-rag-azure-ai-search-python",
+ name: TemplateNames.CustomCopilotRagAzureAISearch,
+ language: "python",
+ description: "",
+ },
{
id: "custom-copilot-rag-microsoft365-ts",
name: TemplateNames.CustomCopilotRagMicrosoft365,
diff --git a/templates/vsc/python/basic-tab/.gitignore b/templates/vsc/python/basic-tab/.gitignore
new file mode 100644
index 00000000000..a8ad39ce7c1
--- /dev/null
+++ b/templates/vsc/python/basic-tab/.gitignore
@@ -0,0 +1,21 @@
+# TeamsFx files
+env/.env.*.user
+env/.env.local
+env/.env.playground
+.env
+appPackage/build
+
+# python virtual environment
+.venv/
+__pycache__/
+
+# others
+.deployment/
+node_modules/
+devTools/*.log
+
+# Dev tool directories
+/devTools/
+
+node_modules
+dist
\ No newline at end of file
diff --git a/templates/vsc/python/basic-tab/.vscode/extensions.json b/templates/vsc/python/basic-tab/.vscode/extensions.json
new file mode 100644
index 00000000000..aac0a6e3470
--- /dev/null
+++ b/templates/vsc/python/basic-tab/.vscode/extensions.json
@@ -0,0 +1,5 @@
+{
+ "recommendations": [
+ "TeamsDevApp.ms-teams-vscode-extension"
+ ]
+}
diff --git a/templates/vsc/python/basic-tab/.vscode/launch.json b/templates/vsc/python/basic-tab/.vscode/launch.json
new file mode 100644
index 00000000000..d3601a2e53b
--- /dev/null
+++ b/templates/vsc/python/basic-tab/.vscode/launch.json
@@ -0,0 +1,243 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch Remote in Teams (Edge)",
+ "type": "msedge",
+ "request": "launch",
+ "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
+ "presentation": {
+ "group": "group 1: Teams",
+ "order": 3
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Launch Remote in Teams (Chrome)",
+ "type": "chrome",
+ "request": "launch",
+ "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
+ "presentation": {
+ "group": "group 1: Teams",
+ "order": 3
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Launch Remote in Outlook (Edge)",
+ "type": "msedge",
+ "request": "launch",
+ "url": "https://outlook.office.com/host/${{M365_APP_ID}}?${account-hint}",
+ "presentation": {
+ "group": "group 2: Outlook",
+ "order": 3
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Launch Remote in Outlook (Chrome)",
+ "type": "chrome",
+ "request": "launch",
+ "url": "https://outlook.office.com/host/${{M365_APP_ID}}?${account-hint}",
+ "presentation": {
+ "group": "group 2: Outlook",
+ "order": 3
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Launch Remote in the Microsoft 365 app (Edge)",
+ "type": "msedge",
+ "request": "launch",
+ "url": "https://www.office.com/m365apps/${{M365_APP_ID}}?auth=2&${account-hint}",
+ "presentation": {
+ "group": "group 3: the Microsoft 365 app",
+ "order": 3
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Launch Remote in the Microsoft 365 app (Chrome)",
+ "type": "chrome",
+ "request": "launch",
+ "url": "https://www.office.com/m365apps/${{M365_APP_ID}}?auth=2&${account-hint}",
+ "presentation": {
+ "group": "group 3: the Microsoft 365 app",
+ "order": 3
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Attach to Frontend in Teams (Edge)",
+ "type": "msedge",
+ "request": "launch",
+ "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
+ "cascadeTerminateToConfigurations": [
+ "Start Python"
+ ],
+ "presentation": {
+ "group": "all",
+ "hidden": true
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Attach to Frontend in Teams (Chrome)",
+ "type": "chrome",
+ "request": "launch",
+ "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
+ "cascadeTerminateToConfigurations": [
+ "Start Python"
+ ],
+ "presentation": {
+ "group": "all",
+ "hidden": true
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Attach to Frontend in Outlook (Edge)",
+ "type": "msedge",
+ "request": "launch",
+ "url": "https://outlook.office.com/host/${{local:M365_APP_ID}}?${account-hint}",
+ "cascadeTerminateToConfigurations": [
+ "Start Python"
+ ],
+ "presentation": {
+ "group": "all",
+ "hidden": true
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Attach to Frontend in Outlook (Chrome)",
+ "type": "chrome",
+ "request": "launch",
+ "url": "https://outlook.office.com/host/${{local:M365_APP_ID}}?${account-hint}",
+ "cascadeTerminateToConfigurations": [
+ "Start Python"
+ ],
+ "presentation": {
+ "group": "all",
+ "hidden": true
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Attach to Frontend in the Microsoft 365 app (Edge)",
+ "type": "msedge",
+ "request": "launch",
+ "url": "https://www.office.com/m365apps/${{local:M365_APP_ID}}?auth=2&${account-hint}",
+ "cascadeTerminateToConfigurations": [
+ "Start Python"
+ ],
+ "presentation": {
+ "group": "all",
+ "hidden": true
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Attach to Frontend in the Microsoft 365 app (Chrome)",
+ "type": "chrome",
+ "request": "launch",
+ "url": "https://www.office.com/m365apps/${{local:M365_APP_ID}}?auth=2&${account-hint}",
+ "cascadeTerminateToConfigurations": [
+ "Start Python"
+ ],
+ "presentation": {
+ "group": "all",
+ "hidden": true
+ },
+ "internalConsoleOptions": "neverOpen"
+ },
+ {
+ "name": "Start Python",
+ "type": "debugpy",
+ "request": "launch",
+ "program": "${workspaceFolder}/src/app.py",
+ "cwd": "${workspaceFolder}/src",
+ "console": "integratedTerminal"
+ },
+ ],
+ "compounds": [
+ {
+ "name": "Debug in Teams (Edge)",
+ "configurations": [
+ "Attach to Frontend in Teams (Edge)",
+ "Start Python"
+ ],
+ "preLaunchTask": "Start App Locally",
+ "presentation": {
+ "group": "group 1: Teams",
+ "order": 1
+ },
+ "stopAll": true
+ },
+ {
+ "name": "Debug in Teams (Chrome)",
+ "configurations": [
+ "Attach to Frontend in Teams (Chrome)",
+ "Start Python"
+ ],
+ "preLaunchTask": "Start App Locally",
+ "presentation": {
+ "group": "group 1: Teams",
+ "order": 2
+ },
+ "stopAll": true
+ },
+ {
+ "name": "Debug in Outlook (Edge)",
+ "configurations": [
+ "Attach to Frontend in Outlook (Edge)",
+ "Start Python"
+ ],
+ "preLaunchTask": "Start App Locally",
+ "presentation": {
+ "group": "group 2: Outlook",
+ "order": 1
+ },
+ "stopAll": true
+ },
+ {
+ "name": "Debug in Outlook (Chrome)",
+ "configurations": [
+ "Attach to Frontend in Outlook (Chrome)",
+ "Start Python"
+ ],
+ "preLaunchTask": "Start App Locally",
+ "presentation": {
+ "group": "group 2: Outlook",
+ "order": 2
+ },
+ "stopAll": true
+ },
+ {
+ "name": "Debug in the Microsoft 365 app (Edge)",
+ "configurations": [
+ "Attach to Frontend in the Microsoft 365 app (Edge)",
+ "Start Python"
+ ],
+ "preLaunchTask": "Start App Locally",
+ "presentation": {
+ "group": "group 3: the Microsoft 365 app",
+ "order": 1
+ },
+ "stopAll": true
+ },
+ {
+ "name": "Debug in the Microsoft 365 app (Chrome)",
+ "configurations": [
+ "Attach to Frontend in the Microsoft 365 app (Chrome)",
+ "Start Python"
+ ],
+ "preLaunchTask": "Start App Locally",
+ "presentation": {
+ "group": "group 3: the Microsoft 365 app",
+ "order": 2
+ },
+ "stopAll": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/templates/vsc/python/basic-tab/.vscode/settings.json b/templates/vsc/python/basic-tab/.vscode/settings.json
new file mode 100644
index 00000000000..4299620253e
--- /dev/null
+++ b/templates/vsc/python/basic-tab/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+ "debug.onTaskErrors": "abort",
+ "json.schemas": [
+ {
+ "fileMatch": [
+ "/aad.*.json"
+ ],
+ "schema": {}
+ }
+ ]
+}
diff --git a/templates/vsc/python/basic-tab/.vscode/tasks.json b/templates/vsc/python/basic-tab/.vscode/tasks.json
new file mode 100644
index 00000000000..76b7ca43365
--- /dev/null
+++ b/templates/vsc/python/basic-tab/.vscode/tasks.json
@@ -0,0 +1,79 @@
+// This file is automatically generated by Microsoft 365 Agents Toolkit.
+// The teamsfx tasks defined in this file require Microsoft 365 Agents Toolkit version >= 5.0.0.
+// See https://aka.ms/teamsfx-tasks for details on how to customize each task.
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Start App Locally",
+ "dependsOn": [
+ "Validate prerequisites",
+ "Start local tunnel",
+ "Provision",
+ "Deploy"
+ ],
+ "dependsOrder": "sequence"
+ },
+ {
+ // Check all required prerequisites.
+ // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args.
+ "label": "Validate prerequisites",
+ "type": "teamsfx",
+ "command": "debug-check-prerequisites",
+ "args": {
+ "prerequisites": [
+ "nodejs", // Validate if Node.js is installed.
+ "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission.
+ "portOccupancy" // Validate available ports to ensure those debug ones are not occupied.
+ ],
+ "portOccupancy": [
+ 53000
+ ]
+ }
+ },
+ {
+ // Start the local tunnel service to forward public URL to local port and inspect traffic.
+ // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions.
+ "label": "Start local tunnel",
+ "type": "teamsfx",
+ "command": "debug-start-local-tunnel",
+ "args": {
+ "type": "dev-tunnel",
+ "ports": [
+ {
+ "portNumber": 53000,
+ "protocol": "http",
+ "access": "public",
+ "writeToEnvironmentFile": {
+ "endpoint": "TAB_ENDPOINT", // output tunnel endpoint as TAB_ENDPOINT
+ "domain": "TAB_DOMAIN" // output tunnel domain as TAB_DOMAIN
+ }
+ }
+ ],
+ "env": "local"
+ },
+ "isBackground": true,
+ "problemMatcher": "$teamsfx-local-tunnel-watch"
+ },
+ {
+ // Create the debug resources.
+ // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args.
+ "label": "Provision",
+ "type": "teamsfx",
+ "command": "provision",
+ "args": {
+ "env": "local"
+ }
+ },
+ {
+ // Build project.
+ // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args.
+ "label": "Deploy",
+ "type": "teamsfx",
+ "command": "deploy",
+ "args": {
+ "env": "local"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/templates/vsc/python/basic-tab/.webappignore b/templates/vsc/python/basic-tab/.webappignore
new file mode 100644
index 00000000000..a93f2c5f650
--- /dev/null
+++ b/templates/vsc/python/basic-tab/.webappignore
@@ -0,0 +1,10 @@
+.venv/
+.vscode/
+.env
+env/
+__pycache__/
+README.md
+m365agents.yml
+m365agents.local.yml
+m365agents.playground.yml
+/devTools/
\ No newline at end of file
diff --git a/templates/vsc/python/basic-tab/README.md.tpl b/templates/vsc/python/basic-tab/README.md.tpl
new file mode 100644
index 00000000000..fcc57fd906f
--- /dev/null
+++ b/templates/vsc/python/basic-tab/README.md.tpl
@@ -0,0 +1,67 @@
+# Overview of the Basic Tab template
+
+This template showcases how Microsoft Teams supports the ability to run web-based UI inside "custom tabs" that users can install either for just themselves (personal tabs) or within a team or group chat context.
+
+## Get started with the template
+
+> **Prerequisites**
+>
+> To run the template in your local dev machine, you will need:
+>
+> - [Python](https://www.python.org/) v3.12 or higher.
+> - [Node.js](https://nodejs.org/), supported versions: >=20.
+> - [Python extension](https://code.visualstudio.com/docs/languages/python), version v2024.0.1 or higher.
+> - [Microsoft 365 Agents Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) latest version or [Microsoft 365 Agents Toolkit CLI](https://aka.ms/teams-toolkit-cli).
+> - A [Microsoft 365 account for development](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts).
+
+### Configurations
+1. Open the command box and enter `Python: Create Environment` to create and activate your desired virtual environment. Remember to select `src/requirements.txt` as dependencies to install when creating the virtual environment.
+
+### Conversation with bot
+1. Select the Microsoft 365 Agents Toolkit icon on the left in the VS Code toolbar.
+1. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already.
+1. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`.
+1. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams.
+1. You can send any message to get a response.
+
+**Congratulations**! You are running an application that can now interact with users in Teams:
+
+> For local debugging using Microsoft 365 Agents Toolkit CLI, you need to do some extra steps described in [Set up your Microsoft 365 Agents Toolkit CLI for local debugging](https://aka.ms/teamsfx-cli-debugging).
+
+
+
+## What's included in the template
+
+| Folder | Contents |
+| - | - |
+| `.vscode` | VSCode files for debugging |
+| `appPackage` | Templates for the application manifest |
+| `env` | Environment files |
+| `infra` | Templates for provisioning Azure resources |
+| `src` | The source code for the application |
+
+The following files can be customized and demonstrate an example implementation to get you started.
+
+| File | Contents |
+| - | - |
+|`src/app.py`| Starts the app.|
+|`src/Web/App.css`| CSS file for the app. |
+|`src/Web/App.tsx`| Tab source file. It calls `teamsjs` SDK to get the context of on which Microsoft 365 application your app is running. |
+|`vite.config.js` | Configuration for Vite build tool. |
+
+The following are Microsoft 365 Agents Toolkit specific project files. You can [visit a complete guide on Github](https://github.com/OfficeDev/TeamsFx/wiki/Teams-Toolkit-Visual-Studio-Code-v5-Guide#overview) to understand how Microsoft 365 Agents Toolkit works.
+
+| File | Contents |
+| - | - |
+|`m365agents.yml`|This is the main Microsoft 365 Agents Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. |
+|`m365agents.local.yml`|This overrides `m365agents.yml` with actions that enable local execution and debugging.|
+|`m365agents.playground.yml`|This overrides `m365agents.yml` with actions that enable local execution and debugging in Microsoft 365 Agents Playground.|
+
+## Additional information and references
+
+- [Microsoft 365 Agents Toolkit Documentations](https://docs.microsoft.com/microsoftteams/platform/toolkit/teams-toolkit-fundamentals)
+- [Microsoft 365 Agents Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli)
+- [Microsoft 365 Agents Toolkit Samples](https://github.com/OfficeDev/TeamsFx-Samples)
+
+## Known issue
+- When you use `Launch Remote in Teams` to remote debug after deployment, you might loose interaction with your agent. This is because the remote service needs to restart. Please wait for several minutes to retry it.
diff --git a/templates/vsc/python/basic-tab/appPackage/color.png b/templates/vsc/python/basic-tab/appPackage/color.png
new file mode 100644
index 00000000000..01aa37e347d
Binary files /dev/null and b/templates/vsc/python/basic-tab/appPackage/color.png differ
diff --git a/templates/vsc/python/basic-tab/appPackage/manifest.json.tpl b/templates/vsc/python/basic-tab/appPackage/manifest.json.tpl
new file mode 100644
index 00000000000..4e57696f377
--- /dev/null
+++ b/templates/vsc/python/basic-tab/appPackage/manifest.json.tpl
@@ -0,0 +1,47 @@
+{
+ "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json",
+ "manifestVersion": "1.23",
+ "version": "1.0.0",
+ "id": "${{TEAMS_APP_ID}}",
+ "developer": {
+ "name": "My App, Inc.",
+ "websiteUrl": "https://www.example.com",
+ "privacyUrl": "https://www.example.com/privacy",
+ "termsOfUseUrl": "https://www.example.com/termsofuse"
+ },
+ "icons": {
+ "color": "color.png",
+ "outline": "outline.png"
+ },
+ "name": {
+ "short": "{{appName}}${{APP_NAME_SUFFIX}}",
+ "full": "Full name for {{appName}}"
+ },
+ "description": {
+ "short": "Short description of {{appName}}",
+ "full": "Full description of {{appName}}"
+ },
+ "accentColor": "#FFFFFF",
+ "bots": [],
+ "composeExtensions": [],
+ "staticTabs": [
+ {
+ "entityId": "index0",
+ "name": "Test",
+ "contentUrl": "${{TAB_ENDPOINT}}/tabs/test",
+ "websiteUrl": "${{TAB_ENDPOINT}}/tabs/test",
+ "scopes": [
+ "personal",
+ "groupChat",
+ "team"
+ ]
+ }
+ ],
+ "permissions": [
+ "identity",
+ "messageTeamMembers"
+ ],
+ "validDomains": [
+ "${{TAB_DOMAIN}}"
+ ]
+}
\ No newline at end of file
diff --git a/templates/vsc/python/basic-tab/appPackage/outline.png b/templates/vsc/python/basic-tab/appPackage/outline.png
new file mode 100644
index 00000000000..f7a4c864475
Binary files /dev/null and b/templates/vsc/python/basic-tab/appPackage/outline.png differ
diff --git a/templates/vsc/python/basic-tab/env/.env.dev b/templates/vsc/python/basic-tab/env/.env.dev
new file mode 100644
index 00000000000..e9abb5303c3
--- /dev/null
+++ b/templates/vsc/python/basic-tab/env/.env.dev
@@ -0,0 +1,15 @@
+# This file includes environment variables that will be committed to git by default.
+
+# Built-in environment variables
+TEAMSFX_ENV=dev
+APP_NAME_SUFFIX=dev
+
+# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups.
+AZURE_SUBSCRIPTION_ID=
+AZURE_RESOURCE_GROUP_NAME=
+RESOURCE_SUFFIX=
+
+# Generated during provision, you can also add your own variables.
+TEAMS_APP_ID=
+TAB_AZURE_APP_SERVICE_RESOURCE_ID=
+TAB_ENDPOINT=
diff --git a/templates/vsc/python/basic-tab/infra/azure.bicep b/templates/vsc/python/basic-tab/infra/azure.bicep
new file mode 100644
index 00000000000..88dd568ba93
--- /dev/null
+++ b/templates/vsc/python/basic-tab/infra/azure.bicep
@@ -0,0 +1,65 @@
+@maxLength(20)
+@minLength(4)
+@description('Used to generate names for all resources in this file')
+param resourceBaseName string
+
+param webAppSKU string
+param linuxFxVersion string
+
+param serverfarmsName string = resourceBaseName
+param webAppName string = resourceBaseName
+param location string = resourceGroup().location
+param pythonVersion string = linuxFxVersion
+
+// Compute resources for your Web App
+resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = {
+ kind: 'app,linux'
+ location: location
+ name: serverfarmsName
+ sku: {
+ name: webAppSKU
+ }
+ properties:{
+ reserved: true
+ }
+}
+
+// Web App that hosts your agent
+resource webApp 'Microsoft.Web/sites@2021-02-01' = {
+ kind: 'app,linux'
+ location: location
+ name: webAppName
+ properties: {
+ serverFarmId: serverfarm.id
+ httpsOnly: true
+ siteConfig: {
+ alwaysOn: true
+ appCommandLine: 'python app.py'
+ linuxFxVersion: pythonVersion
+ appSettings: [
+ {
+ name: 'WEBSITES_CONTAINER_START_TIME_LIMIT'
+ value: '900'
+ }
+ {
+ name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
+ value: 'true'
+ }
+ {
+ name: 'PORT'
+ value: '53000'
+ }
+ {
+ name: 'RUNNING_ON_AZURE'
+ value: '1'
+ }
+ ]
+ ftpsState: 'FtpsOnly'
+ }
+ }
+}
+
+// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details.
+output TAB_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id // used in deploy stage
+output TAB_DOMAIN string = webApp.properties.defaultHostName
+output TAB_ENDPOINT string = 'https://${webApp.properties.defaultHostName}'
\ No newline at end of file
diff --git a/templates/vsc/python/basic-tab/infra/azure.parameters.json b/templates/vsc/python/basic-tab/infra/azure.parameters.json
new file mode 100644
index 00000000000..bcc435a7383
--- /dev/null
+++ b/templates/vsc/python/basic-tab/infra/azure.parameters.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "resourceBaseName": {
+ "value": "tab${{RESOURCE_SUFFIX}}"
+ },
+ "webAppSKU": {
+ "value": "B3"
+ },
+ "linuxFxVersion": {
+ "value": "PYTHON|3.12"
+ }
+ }
+ }
\ No newline at end of file
diff --git a/templates/vsc/python/basic-tab/m365agents.local.yml.tpl b/templates/vsc/python/basic-tab/m365agents.local.yml.tpl
new file mode 100644
index 00000000000..1c92ab11b65
--- /dev/null
+++ b/templates/vsc/python/basic-tab/m365agents.local.yml.tpl
@@ -0,0 +1,68 @@
+# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/yaml.schema.json
+# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
+# Visit https://aka.ms/teamsfx-actions for details on actions
+version: v1.9
+
+provision:
+ # Creates an app
+ - uses: teamsApp/create
+ with:
+ # app name
+ name: {{appName}}${{APP_NAME_SUFFIX}}
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ teamsAppId: TEAMS_APP_ID
+
+ # Build app package with latest env value
+ - uses: teamsApp/zipAppPackage
+ with:
+ # Path to manifest template
+ manifestPath: ./appPackage/manifest.json
+ outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ outputFolder: ./appPackage/build
+
+ # Validate app package using validation rules
+ - uses: teamsApp/validateAppPackage
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Apply the app manifest to an existing app in
+ # Developer Portal.
+ # Will use the app id in manifest file to determine which app to update.
+ - uses: teamsApp/update
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Extend your app to Outlook and the Microsoft 365 app
+ - uses: teamsApp/extendToM365
+ with:
+ # Relative path to the build app package.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ titleId: M365_TITLE_ID
+ appId: M365_APP_ID
+
+deploy:
+ # Run npm command
+ - uses: cli/runNpmCommand
+ with:
+ args: install --no-audit
+ workingDirectory: ./src/Web
+
+ - uses: cli/runNpmCommand
+ name: build app
+ with:
+ args: run build --if-present
+ workingDirectory: ./src/Web
+
+ # Generate runtime environment variables
+ - uses: file/createOrUpdateEnvironmentFile
+ with:
+ target: ./.env
+ envs:
+ PORT: 53000
diff --git a/templates/vsc/python/basic-tab/m365agents.yml.tpl b/templates/vsc/python/basic-tab/m365agents.yml.tpl
new file mode 100644
index 00000000000..752a5b0b8ab
--- /dev/null
+++ b/templates/vsc/python/basic-tab/m365agents.yml.tpl
@@ -0,0 +1,149 @@
+# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/yaml.schema.json
+# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
+# Visit https://aka.ms/teamsfx-actions for details on actions
+version: v1.9
+
+environmentFolderPath: ./env
+
+# Triggered when 'teamsfx provision' is executed
+provision:
+ # Creates an app
+ - uses: teamsApp/create
+ with:
+ # app name
+ name: {{appName}}${{APP_NAME_SUFFIX}}
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ teamsAppId: TEAMS_APP_ID
+
+ - uses: arm/deploy # Deploy given ARM templates parallelly.
+ with:
+ # AZURE_SUBSCRIPTION_ID is a built-in environment variable,
+ # if its value is empty, TeamsFx will prompt you to select a subscription.
+ # Referencing other environment variables with empty values
+ # will skip the subscription selection prompt.
+ subscriptionId: ${{AZURE_SUBSCRIPTION_ID}}
+ # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable,
+ # if its value is empty, TeamsFx will prompt you to select or create one
+ # resource group.
+ # Referencing other environment variables with empty values
+ # will skip the resource group selection prompt.
+ resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}}
+ templates:
+ - path: ./infra/azure.bicep # Relative path to this file
+ # Relative path to this yaml file.
+ # Placeholders will be replaced with corresponding environment
+ # variable before ARM deployment.
+ parameters: ./infra/azure.parameters.json
+ # Required when deploying ARM template
+ deploymentName: Create-resources-for-tab
+ # Microsoft 365 Agents Toolkit will download this bicep CLI version from github for you,
+ # will use bicep CLI in PATH if you remove this config.
+ bicepCliVersion: v0.9.1
+
+ # Validate using manifest schema
+ - uses: teamsApp/validateManifest
+ with:
+ # Path to manifest template
+ manifestPath: ./appPackage/manifest.json
+
+ # Build app package with latest env value
+ - uses: teamsApp/zipAppPackage
+ with:
+ # Path to manifest template
+ manifestPath: ./appPackage/manifest.json
+ outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ outputFolder: ./appPackage/build
+
+ # Validate app package using validation rules
+ - uses: teamsApp/validateAppPackage
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Apply the app manifest to an existing app in
+ # Developer Portal.
+ # Will use the app id in manifest file to determine which app to update.
+ - uses: teamsApp/update
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Extend your app to Outlook and the Microsoft 365 app
+ - uses: teamsApp/extendToM365
+ with:
+ # Relative path to the build app package.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ titleId: M365_TITLE_ID
+ appId: M365_APP_ID
+
+# Triggered when 'teamsfx deploy' is executed
+deploy:
+ # Run npm command
+ - uses: cli/runNpmCommand
+ with:
+ args: install
+ workingDirectory: ./src/Web
+
+ - uses: cli/runNpmCommand
+ name: build app
+ with:
+ args: run build
+ workingDirectory: ./src/Web
+ # Deploy your application to Azure App Service using the zip deploy feature.
+ # For additional details, refer to https://aka.ms/zip-deploy-to-app-services.
+ - uses: azureAppService/zipDeploy
+ with:
+ # Deploy base folder
+ artifactFolder: src
+ # Ignore file location, leave blank will ignore nothing
+ ignoreFile: .webappignore
+ # The resource id of the cloud resource to be deployed to.
+ # This key will be generated by arm/deploy action automatically.
+ # You can replace it with your existing Azure Resource id
+ # or add it to your environment variable file.
+ resourceId: ${{TAB_AZURE_APP_SERVICE_RESOURCE_ID}}
+
+# Triggered when 'teamsapp publish' is executed
+publish:
+ # Validate using manifest schema
+ - uses: teamsApp/validateManifest
+ with:
+ # Path to manifest template
+ manifestPath: ./appPackage/manifest.json
+
+ # Build app package with latest env value
+ - uses: teamsApp/zipAppPackage
+ with:
+ # Path to manifest template
+ manifestPath: ./appPackage/manifest.json
+ outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ outputFolder: ./appPackage/build
+
+ # Validate app package using validation rules
+ - uses: teamsApp/validateAppPackage
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Apply the app manifest to an existing app in
+ # Developer Portal.
+ # Will use the app id in manifest file to determine which app to update.
+ - uses: teamsApp/update
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ # Publish the app to
+ # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps)
+ # for review and approval
+ - uses: teamsApp/publishAppPackage
+ with:
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ publishedAppId: TEAMS_APP_PUBLISHED_APP_ID
diff --git a/templates/vsc/python/basic-tab/src/Web/index.html b/templates/vsc/python/basic-tab/src/Web/index.html
new file mode 100644
index 00000000000..0d0b54096cd
--- /dev/null
+++ b/templates/vsc/python/basic-tab/src/Web/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Test
+
+
+
+
+
+
+
diff --git a/templates/vsc/python/basic-tab/src/Web/package.json b/templates/vsc/python/basic-tab/src/Web/package.json
new file mode 100644
index 00000000000..1ccbcaa43b5
--- /dev/null
+++ b/templates/vsc/python/basic-tab/src/Web/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "tab",
+ "license": "MIT",
+ "private": true,
+ "main": "bin/index",
+ "types": "bin/index",
+ "files": [
+ "bin",
+ "README.md"
+ ],
+ "scripts": {
+ "clean": "npx rimraf ./bin",
+ "build": "npx vite build"
+ },
+ "dependencies": {
+ "@microsoft/teams.client": "^2.0.0",
+ "@microsoft/teams.common": "^2.0.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.3.4",
+ "rimraf": "^6.0.1",
+ "typescript": "^5.4.5",
+ "vite": "^6.3.6",
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9"
+ }
+}
diff --git a/templates/vsc/python/basic-tab/src/Web/src/App.css b/templates/vsc/python/basic-tab/src/Web/src/App.css
new file mode 100644
index 00000000000..4d4a5b580ee
--- /dev/null
+++ b/templates/vsc/python/basic-tab/src/Web/src/App.css
@@ -0,0 +1,43 @@
+body {
+ margin: 0px;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, "Apple Color Emoji", "Segoe UI Emoji", sans-serif;
+}
+
+a {
+ color: white;
+}
+
+h1 {
+ font-size: 3rem;
+}
+
+.App {
+ color: white;
+ background-color: #282c34;
+ display: flex;
+ flex-direction: column;
+ row-gap: 1rem;
+ justify-content: start;
+ align-items: center;
+ height: 100vh;
+ overflow: hidden auto;
+}
+
+.App > * {
+ max-width: calc(100% - 4rem);
+}
+
+.actions {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ column-gap: 1rem;
+}
+
+.result {
+ color: white;
+ background-color: black;
+ padding: 0 1rem;
+ overflow: auto;
+}
diff --git a/templates/vsc/python/basic-tab/src/Web/src/App.tsx b/templates/vsc/python/basic-tab/src/Web/src/App.tsx
new file mode 100644
index 00000000000..da986b743db
--- /dev/null
+++ b/templates/vsc/python/basic-tab/src/Web/src/App.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+import * as teamsJs from "@microsoft/teams-js";
+
+import "./App.css";
+
+export default function App() {
+ const [content, setContent] = React.useState("");
+
+ React.useEffect(() => {
+ (async () => {
+ teamsJs.app.initialize().then(() => {
+ teamsJs.app.getContext().then((context: teamsJs.app.Context) => {
+ if (context?.app?.host?.name) {
+ setContent(`Your app is running in ${context.app.host.name}`);
+ }
+ });
+ });
+ })();
+ }, []);
+
+ return (
+
+ );
+}
diff --git a/templates/vsc/python/basic-tab/src/Web/src/client.tsx b/templates/vsc/python/basic-tab/src/Web/src/client.tsx
new file mode 100644
index 00000000000..5f6cb9fd0f5
--- /dev/null
+++ b/templates/vsc/python/basic-tab/src/Web/src/client.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+
+import App from "./App";
+
+createRoot(document.getElementById("root")!).render(
+
+
+
+);
diff --git a/templates/vsc/python/basic-tab/src/Web/src/vite-env.d.ts b/templates/vsc/python/basic-tab/src/Web/src/vite-env.d.ts
new file mode 100644
index 00000000000..11f02fe2a00
--- /dev/null
+++ b/templates/vsc/python/basic-tab/src/Web/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/templates/vsc/python/basic-tab/src/Web/tsconfig.json b/templates/vsc/python/basic-tab/src/Web/tsconfig.json
new file mode 100644
index 00000000000..3f79e4b3924
--- /dev/null
+++ b/templates/vsc/python/basic-tab/src/Web/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "outDir": "bin",
+ "useDefineForClassFields": true,
+ "lib": [ "ES2020", "DOM", "DOM.Iterable" ],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true,
+ "forceConsistentCasingInFileNames": false
+ },
+ "include": [ "./src/**/*.ts", "./src/**/*.tsx", "./src/**/*.d.ts" ]
+}
diff --git a/templates/vsc/python/basic-tab/src/Web/vite.config.js b/templates/vsc/python/basic-tab/src/Web/vite.config.js
new file mode 100644
index 00000000000..1dfbfde6da2
--- /dev/null
+++ b/templates/vsc/python/basic-tab/src/Web/vite.config.js
@@ -0,0 +1,11 @@
+import fs from "fs";
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ base: "/tabs/test",
+ esbuild: {
+ tsconfigRaw: fs.readFileSync("./tsconfig.json"),
+ },
+});
diff --git a/templates/vsc/python/basic-tab/src/app.py b/templates/vsc/python/basic-tab/src/app.py
new file mode 100644
index 00000000000..6e9f6678653
--- /dev/null
+++ b/templates/vsc/python/basic-tab/src/app.py
@@ -0,0 +1,16 @@
+"""
+Copyright (c) Microsoft Corporation. All rights reserved.
+Licensed under the MIT License.
+"""
+
+import asyncio
+from pathlib import Path
+from typing import Any
+
+from microsoft.teams.apps import App
+
+app = App()
+app.tab("test", str(Path("Web/dist").resolve()))
+
+if __name__ == "__main__":
+ asyncio.run(app.start())
\ No newline at end of file
diff --git a/templates/vsc/python/basic-tab/src/requirements.txt b/templates/vsc/python/basic-tab/src/requirements.txt
new file mode 100644
index 00000000000..018f471b213
--- /dev/null
+++ b/templates/vsc/python/basic-tab/src/requirements.txt
@@ -0,0 +1,3 @@
+dotenv>=0.9.9
+microsoft-teams-apps>=2.0.0a2,<3.0.0
+microsoft-teams-api>=2.0.0a2,<3.0.0
\ No newline at end of file
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/README.md.tpl b/templates/vsc/python/custom-copilot-rag-azure-ai-search/README.md.tpl
index 23857dd591b..d60c71e45dd 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/README.md.tpl
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/README.md.tpl
@@ -4,11 +4,7 @@ This app template showcases how to build one of the most powerful applications e
This app template also demonstrates usage of techniques like:
- [Retrieval Augmented Generation](https://python.langchain.com/docs/use_cases/question_answering/#what-is-rag), or RAG.
- [Azure AI Search](https://learn.microsoft.com/azure/search/search-what-is-azure-search)
-- [Teams AI Library](https://learn.microsoft.com/microsoftteams/platform/bots/how-to/teams%20conversational%20ai/teams-conversation-ai-overview)
-
-> **Note**
->
-> [Teams AI library V2](https://aka.ms/teams-ai-library-v2) is recommended. This template will be upgraded to Teams AI V2 soon.
+- [Teams AI Library V2](https://aka.ms/teams-ai-library-v2)
## Get started with the template
@@ -16,7 +12,7 @@ This app template also demonstrates usage of techniques like:
>
> To run the template in your local dev machine, you will need:
>
-> - [Python](https://www.python.org/), version 3.8 to 3.11.
+> - [Python](https://www.python.org/), version 3.12 or higher.
> - [Python extension](https://code.visualstudio.com/docs/languages/python), version v2024.0.1 or higher.
> - [Microsoft 365 Agents Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) latest version or [Microsoft 365 Agents Toolkit CLI](https://aka.ms/teams-toolkit-cli).
{{#useAzureOpenAI}}
@@ -88,12 +84,10 @@ The following files can be customized and demonstrate an example implementation
| File | Contents |
| - | - |
-|`src/bot.py`| Handles business logics for the AI Search Bot.|
|`src/config.py`| Defines the environment variables.|
|`src/app.py`| Main module of the AI Search Bot, hosts a aiohttp api server for the app.|
|`src/azure_ai_search_data_source.py.py`| Handles data search logics.|
-|`src/prompts/chat/skprompt.txt`| Defines the prompt.|
-|`src/prompts/chat/config.json`| Configures the prompt.|
+|`src/instructions.txt`| Defines the prompt.|
The following files are scripts and raw texts that help you to prepare or clean data source in Azure Search.
@@ -114,11 +108,7 @@ The following are Microsoft 365 Agents Toolkit specific project files. You can [
## Extend the template
-- Follow [Build a Basic AI Chatbot in Teams](https://aka.ms/teamsfx-basic-ai-chatbot) to extend the template with more AI capabilities.
-- Follow [Build a RAG Bot in Teams](https://aka.ms/teamsfx-rag-bot) to extend the template with more RAG capabilities. In this template, we upload raw text data to Azure AI Search. Azure AI Search also allows you to create vectorized data and do vector similarity search.
-You can refer to the section [integrate-vectorization](https://github.com/OfficeDev/TeamsFx/wiki/Build-a-RAG-Bot-in-Teams#integrate-vectorization) or the demo [integrated-vectorization](https://github.com/Azure/azure-search-vector-samples/tree/main/demo-python/code/integrated-vectorization) for more details.
-- Understand more about [Azure AI Search as data source](https://aka.ms/teamsfx-rag-bot#azure-ai-search-as-data-source).
-
+To extend the Basic AI Chatbot template with more AI capabilities, explore [Teams AI library V2 documentation](https://aka.ms/m365-agents-toolkit/teams-agent-extend-ai-python).
## Additional information and references
- [Microsoft 365 Agents Toolkit Documentations](https://docs.microsoft.com/microsoftteams/platform/toolkit/teams-toolkit-fundamentals)
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/infra/azure.bicep.tpl b/templates/vsc/python/custom-copilot-rag-azure-ai-search/infra/azure.bicep.tpl
index 578848dfb71..5a4deadfcd0 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/infra/azure.bicep.tpl
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/infra/azure.bicep.tpl
@@ -61,19 +61,19 @@ resource webApp 'Microsoft.Web/sites@2021-02-01' = {
serverFarmId: serverfarm.id
siteConfig: {
alwaysOn: true
- appCommandLine: 'gunicorn --bind 0.0.0.0 --worker-class aiohttp.worker.GunicornWebWorker --timeout 600 app:app'
+ appCommandLine: 'python app.py'
linuxFxVersion: pythonVersion
appSettings: [
{
name: 'WEBSITES_CONTAINER_START_TIME_LIMIT'
- value: '600'
+ value: '900'
}
{
name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
value: 'true'
}
{
- name: 'BOT_ID'
+ name: 'CLIENT_ID'
value: identity.properties.clientId
}
{{#useAzureOpenAI}}
@@ -109,7 +109,7 @@ resource webApp 'Microsoft.Web/sites@2021-02-01' = {
value: azureSearchEndpoint
}
{
- name: 'BOT_TENANT_ID'
+ name: 'TENANT_ID'
value: identity.properties.tenantId
}
{
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/infra/azure.parameters.json.tpl b/templates/vsc/python/custom-copilot-rag-azure-ai-search/infra/azure.parameters.json.tpl
index 68468f2d1e8..eef5153b01c 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/infra/azure.parameters.json.tpl
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/infra/azure.parameters.json.tpl
@@ -31,13 +31,13 @@
"value": "${{AZURE_SEARCH_ENDPOINT}}"
},
"webAppSKU": {
- "value": "B1"
+ "value": "B3"
},
"botDisplayName": {
"value": "{{appName}}"
},
"linuxFxVersion": {
- "value": "PYTHON|3.11"
+ "value": "PYTHON|3.12"
}
}
}
\ No newline at end of file
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.local.yml.tpl b/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.local.yml.tpl
index 5c50d6ffef2..aebe2852a90 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.local.yml.tpl
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.local.yml.tpl
@@ -87,8 +87,8 @@ deploy:
with:
target: ./.env
envs:
- BOT_ID: ${{BOT_ID}}
- BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}}
+ CLIENT_ID: ${{BOT_ID}}
+ CLIENT_SECRET: ${{SECRET_BOT_PASSWORD}}
{{#useAzureOpenAI}}
AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}}
AZURE_OPENAI_MODEL_DEPLOYMENT_NAME: ${{AZURE_OPENAI_MODEL_DEPLOYMENT_NAME}}
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.playground.yml.tpl b/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.playground.yml.tpl
index 275343601b1..8f0fe46c950 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.playground.yml.tpl
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.playground.yml.tpl
@@ -19,8 +19,8 @@ deploy:
target: ./.env
envs:
TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}}
- BOT_ID: ""
- BOT_PASSWORD: ""
+ CLIENT_ID: ""
+ CLIENT_SECRET: ""
{{#useAzureOpenAI}}
AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}}
AZURE_OPENAI_MODEL_DEPLOYMENT_NAME: ${{AZURE_OPENAI_MODEL_DEPLOYMENT_NAME}}
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.sandbox.yml.tpl b/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.sandbox.yml.tpl
index 596ff68f814..e97df522380 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.sandbox.yml.tpl
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/m365agents.sandbox.yml.tpl
@@ -100,8 +100,8 @@ deploy:
with:
target: ./.env
envs:
- BOT_ID: ${{BOT_ID}}
- BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}}
+ CLIENT_ID: ${{BOT_ID}}
+ CLIENT_SECRET: ${{SECRET_BOT_PASSWORD}}
{{#useAzureOpenAI}}
AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}}
AZURE_OPENAI_MODEL_DEPLOYMENT_NAME: ${{AZURE_OPENAI_MODEL_DEPLOYMENT_NAME}}
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/app.py b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/app.py
deleted file mode 100644
index 910476d0142..00000000000
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/app.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""
-Copyright (c) Microsoft Corporation. All rights reserved.
-Licensed under the MIT License.
-"""
-
-import asyncio
-from http import HTTPStatus
-from aiohttp import web
-from botbuilder.core.integration import aiohttp_error_middleware
-
-from bot import bot_app
-
-routes = web.RouteTableDef()
-
-@routes.post("/api/messages")
-async def on_messages(req: web.Request) -> web.Response:
- res = await bot_app.process(req)
-
- if res is not None:
- return res
-
- return web.Response(status=HTTPStatus.OK)
-
-app = web.Application(middlewares=[aiohttp_error_middleware])
-app.add_routes(routes)
-
-from config import Config
-
-if __name__ == "__main__":
- web.run_app(app, host="localhost", port=Config.PORT)
\ No newline at end of file
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/app.py.tpl b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/app.py.tpl
new file mode 100644
index 00000000000..fcaabe3c1ab
--- /dev/null
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/app.py.tpl
@@ -0,0 +1,149 @@
+import asyncio
+import json
+import os
+
+from azure.identity import ManagedIdentityCredential
+from microsoft.teams.ai import ChatPrompt, ListMemory
+from microsoft.teams.ai.ai_model import AIModel
+from microsoft.teams.apps import App, ActivityContext
+from microsoft.teams.openai import OpenAICompletionsAIModel
+from microsoft.teams.api import CitationAppearance, MessageActivity, MessageActivityInput, MessageSubmitActionInvokeActivity
+
+from config import Config
+from azure_ai_search_data_source import AzureAISearchDataSource, AzureAISearchDataSourceOptions
+
+config = Config()
+
+# Create Azure AI Search options
+search_options = AzureAISearchDataSourceOptions(
+ name="contoso-electronics-search",
+ indexName="contoso-electronics",
+ azureAISearchApiKey=config.AZURE_SEARCH_KEY,
+ azureAISearchEndpoint=config.AZURE_SEARCH_ENDPOINT
+)
+
+azure_ai_search = AzureAISearchDataSource(search_options)
+
+# Load instructions from file
+def load_instructions() -> str:
+ """Load instructions from instructions.txt file"""
+ try:
+ with open(os.path.join(os.path.dirname(__file__), "instructions.txt"), "r", encoding="utf-8") as f:
+ return f.read().strip()
+ except FileNotFoundError:
+ return "You are a helpful assistant."
+
+INSTRUCTIONS = load_instructions()
+
+def create_token_factory():
+ def get_token(scopes, tenant_id=None):
+ credential = ManagedIdentityCredential(client_id=config.APP_ID)
+ if isinstance(scopes, str):
+ scopes_list = [scopes]
+ else:
+ scopes_list = scopes
+ token = credential.get_token(*scopes_list)
+ return token.token
+ return get_token
+
+app = App(
+ token=create_token_factory() if config.APP_TYPE == "UserAssignedMsi" else None
+)
+
+{{#useAzureOpenAI}}
+model = OpenAICompletionsAIModel(
+ key=config.AZURE_OPENAI_API_KEY,
+ model=config.AZURE_OPENAI_MODEL_DEPLOYMENT_NAME,
+ azure_endpoint=config.AZURE_OPENAI_ENDPOINT,
+ api_version="2024-10-21"
+)
+{{/useAzureOpenAI}}
+
+{{#useOpenAI}}
+model = OpenAICompletionsAIModel(
+ key=config.OPENAI_API_KEY,
+ model=config.OPENAI_MODEL_NAME
+)
+{{/useOpenAI}}
+
+conversation_store: dict[str, ListMemory] = {}
+
+def get_or_create_conversation_memory(conversation_id: str) -> ListMemory:
+ """Get or create conversation memory for a specific conversation"""
+ if conversation_id not in conversation_store:
+ conversation_store[conversation_id] = ListMemory()
+ return conversation_store[conversation_id]
+
+async def handle_stateful_conversation(model: AIModel, ctx: ActivityContext[MessageActivity]) -> None:
+ """Example of stateful conversation handler that maintains conversation history"""
+ # Retrieve existing conversation memory or initialize new one
+ memory = get_or_create_conversation_memory(ctx.activity.conversation.id)
+
+ # Get existing messages for logging
+ existing_messages = await memory.get_all()
+ print(f"Existing messages before sending to prompt: {len(existing_messages)} messages")
+
+ input = ctx.activity.strip_mentions_text().text
+ data_context = await azure_ai_search.render_data(input)
+
+ # Create ChatPrompt with conversation-specific memory
+ chat_prompt = ChatPrompt(model)
+
+ chat_result = await chat_prompt.send(
+ input=input,
+ memory=memory,
+ instructions=f"{INSTRUCTIONS}\n\nAdditional Context:\n${data_context.output}"
+ )
+
+ result = None
+ try:
+ # Attempt to parse the response as JSON
+ result = json.loads(chat_result.response.content)
+ except json.JSONDecodeError as error:
+ print(f"Error decoding JSON: {error}")
+ await ctx.send(MessageActivityInput(text=chat_result.response.content).add_ai_generated().add_feedback())
+ return
+
+
+ citations = []
+ position = 1
+ content = ""
+ if result and result.get("results") and len(result["results"]) > 0:
+ for content_item in result["results"]:
+
+ if content_item.get("citationTitle") and len(content_item["citationTitle"]) > 0:
+ content += f"{content_item['answer']}[{position}]
"
+ citations.append(
+ {
+ "id": position,
+ "title": content_item.get("citationTitle", ""),
+ "abstract": content_item.get("citationContent", "")[:160]
+ }
+ )
+ position += 1
+ else:
+ content += f"{content_item['answer']}
"
+
+ message_activity = MessageActivityInput(text=content).add_ai_generated().add_feedback()
+ for citation in citations:
+ message_activity.add_citation(
+ citation["id"],
+ CitationAppearance(name=citation["title"], abstract=citation["abstract"])
+ )
+
+ await ctx.send(message_activity)
+
+@app.on_message
+async def handle_message(ctx: ActivityContext[MessageActivity]):
+ """Handle messages using stateful conversation"""
+ await handle_stateful_conversation(model, ctx)
+
+@app.on_message_submit_feedback
+async def handle_message_feedback(ctx: ActivityContext[MessageSubmitActionInvokeActivity]):
+ """Handle feedback submission events"""
+ activity = ctx.activity
+
+ print(f"your feedback is {activity.value.action_value}")
+
+if __name__ == "__main__":
+ asyncio.run(app.start())
\ No newline at end of file
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/azure_ai_search_data_source.py.tpl b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/azure_ai_search_data_source.py.tpl
index de1b66e88f6..99eb105fbf2 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/azure_ai_search_data_source.py.tpl
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/azure_ai_search_data_source.py.tpl
@@ -1,40 +1,38 @@
from dataclasses import dataclass
from typing import Optional, List
-from azure.search.documents.indexes.models import _edm as EDM
-from azure.search.documents.models import VectorQuery, VectorizedQuery
+from azure.search.documents.models import VectorizedQuery
{{#useAzureOpenAI}}
-from teams.ai.embeddings import AzureOpenAIEmbeddings, AzureOpenAIEmbeddingsOptions
+from openai import AsyncAzureOpenAI
{{/useAzureOpenAI}}
{{#useOpenAI}}
-from teams.ai.embeddings import OpenAIEmbeddings, OpenAIEmbeddingsOptions
+from openai import AsyncOpenAI
{{/useOpenAI}}
-from teams.state.memory import Memory
-from teams.state.state import TurnContext
-from teams.ai.tokenizers import Tokenizer
-from teams.ai.data_sources import DataSource
from config import Config
async def get_embedding_vector(text: str):
{{#useAzureOpenAI}}
- embeddings = AzureOpenAIEmbeddings(AzureOpenAIEmbeddingsOptions(
- azure_api_key=Config.AZURE_OPENAI_API_KEY,
+ client = AsyncAzureOpenAI(
+ api_key=Config.AZURE_OPENAI_API_KEY,
azure_endpoint=Config.AZURE_OPENAI_ENDPOINT,
- azure_deployment=Config.AZURE_OPENAI_EMBEDDING_DEPLOYMENT
- ))
+ api_version="2024-10-21"
+ )
+ result = await client.embeddings.create(
+ model=Config.AZURE_OPENAI_EMBEDDING_DEPLOYMENT,
+ input=text
+ )
{{/useAzureOpenAI}}
{{#useOpenAI}}
- embeddings=OpenAIEmbeddings(OpenAIEmbeddingsOptions(
- api_key=Config.OPENAI_API_KEY,
- model=Config.OPENAI_EMBEDDING_DEPLOYMENT,
- ))
+ client = AsyncOpenAI(api_key=Config.OPENAI_API_KEY)
+ result = await client.embeddings.create(
+ model=Config.OPENAI_EMBEDDING_DEPLOYMENT,
+ input=text
+ )
{{/useOpenAI}}
- result = await embeddings.create_embeddings(text)
- if (result.status != 'success' or not result.output):
+ if not result.data:
raise Exception(f"Failed to generate embeddings for description: {text}")
-
- return result.output[0]
+ return result.data[0].embedding
@dataclass
class Doc:
@@ -56,12 +54,10 @@ import json
@dataclass
class Result:
- def __init__(self, output, length, too_long):
+ def __init__(self, output):
self.output = output
- self.length = length
- self.too_long = too_long
-class AzureAISearchDataSource(DataSource):
+class AzureAISearchDataSource():
def __init__(self, options: AzureAISearchDataSourceOptions):
self.name = options.name
self.options = options
@@ -74,13 +70,12 @@ class AzureAISearchDataSource(DataSource):
def name(self):
return self.name
- async def render_data(self, _context: TurnContext, memory: Memory, tokenizer: Tokenizer, maxTokens: int):
- query = memory.get('temp.input')
+ async def render_data(self, query):
embedding = await get_embedding_vector(query)
vector_query = VectorizedQuery(vector=embedding, k_nearest_neighbors=2, fields="descriptionVector")
if not query:
- return Result('', 0, False)
+ return Result('')
selectedFields = [
'docTitle',
@@ -95,17 +90,12 @@ class AzureAISearchDataSource(DataSource):
)
if not searchResults:
- return Result('', 0, False)
-
- usedTokens = 0
- doc = ''
- for result in searchResults:
- tokens = len(tokenizer.encode(json.dumps(result["description"])))
+ return Result('')
- if usedTokens + tokens > maxTokens:
- break
- doc += json.dumps(result["description"])
- usedTokens += tokens
-
- return Result(doc, usedTokens, usedTokens > maxTokens)
\ No newline at end of file
+ # Convert search results to formatted text
+ docs = []
+ for result in searchResults:
+ docs.append(f"Title: {result.get('docTitle', 'N/A')}\nDescription: {result.get('description', 'N/A')}")
+
+ return Result('\n\n'.join(docs))
\ No newline at end of file
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/bot.py.tpl b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/bot.py.tpl
deleted file mode 100644
index d24c3fae688..00000000000
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/bot.py.tpl
+++ /dev/null
@@ -1,94 +0,0 @@
-import asyncio
-from dataclasses import dataclass, asdict
-import json
-import os
-import sys
-import traceback
-from typing import Generic, TypeVar
-
-
-from botbuilder.core import MemoryStorage, TurnContext
-from teams import Application, ApplicationOptions, TeamsAdapter
-from teams.ai import AIOptions
-from teams.ai.models import AzureOpenAIModelOptions, OpenAIModel, OpenAIModelOptions
-from teams.ai.planners import ActionPlanner, ActionPlannerOptions
-from teams.ai.prompts import PromptManager, PromptManagerOptions
-from teams.ai.actions import ActionTypes
-from teams.state import TurnState
-from teams.feedback_loop_data import FeedbackLoopData
-from teams.ai.actions import ActionTypes, ActionTurnContext
-
-from azure_ai_search_data_source import AzureAISearchDataSource, AzureAISearchDataSourceOptions
-from custom_say_command import say_command
-from config import Config
-
-config = Config()
-
-# Create AI components
-model: OpenAIModel
-
-{{#useAzureOpenAI}}
-model = OpenAIModel(
- AzureOpenAIModelOptions(
- api_key=config.AZURE_OPENAI_API_KEY,
- default_model=config.AZURE_OPENAI_MODEL_DEPLOYMENT_NAME,
- endpoint=config.AZURE_OPENAI_ENDPOINT,
- )
-)
-{{/useAzureOpenAI}}
-{{#useOpenAI}}
-model = OpenAIModel(
- OpenAIModelOptions(
- api_key=config.OPENAI_API_KEY,
- default_model=config.OPENAI_MODEL_NAME,
- )
-)
-{{/useOpenAI}}
-
-prompts = PromptManager(PromptManagerOptions(prompts_folder=f"{os.getcwd()}/prompts"))
-
-prompts.add_data_source(
- AzureAISearchDataSource(
- AzureAISearchDataSourceOptions(
- name='azure-ai-search',
- indexName='contoso-electronics',
- azureAISearchApiKey=config.AZURE_SEARCH_KEY,
- azureAISearchEndpoint=config.AZURE_SEARCH_ENDPOINT,
- )
- )
-)
-
-planner = ActionPlanner(
- ActionPlannerOptions(model=model, prompts=prompts, default_prompt="chat")
-)
-
-# Define storage and application
-storage = MemoryStorage()
-bot_app = Application[TurnState](
- ApplicationOptions(
- bot_app_id=config.APP_ID,
- storage=storage,
- adapter=TeamsAdapter(config),
- ai=AIOptions(planner=planner, enable_feedback_loop=True),
- )
-)
-
-@bot_app.ai.action(ActionTypes.SAY_COMMAND)
-async def on_say(_context: ActionTurnContext, _state: TurnState):
- return await say_command(_context, _state, _context.data, feedback_loop_enabled=True)
-
-@bot_app.error
-async def on_error(context: TurnContext, error: Exception):
- # This check writes out errors to console log .vs. app insights.
- # NOTE: In production environment, you should consider logging this to Azure
- # application insights.
- print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
- traceback.print_exc()
-
- # Send a message to the user
- await context.send_activity("The agent encountered an error or bug.")
-
-@bot_app.feedback_loop()
-async def feedback_loop(_context: TurnContext, _state: TurnState, feedback_loop_data: FeedbackLoopData):
- # Add custom feedback process logic here.
- print(f"Your feedback is:\n{json.dumps(asdict(feedback_loop_data), indent=4)}")
\ No newline at end of file
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/config.py.tpl b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/config.py.tpl
index 1c016b98ec2..eb6de845801 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/config.py.tpl
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/config.py.tpl
@@ -13,10 +13,10 @@ class Config:
"""Bot Configuration"""
PORT = 3978
- APP_ID = os.environ.get("BOT_ID", "")
- APP_PASSWORD = os.environ.get("BOT_PASSWORD", "")
+ APP_ID = os.environ.get("CLIENT_ID", "")
+ APP_PASSWORD = os.environ.get("CLIENT_SECRET", "")
APP_TYPE = os.environ.get("BOT_TYPE", "")
- APP_TENANTID = os.environ.get("BOT_TENANT_ID", "")
+ APP_TENANTID = os.environ.get("TENANT_ID", "")
{{#useAzureOpenAI}}
AZURE_OPENAI_API_KEY = os.environ["AZURE_OPENAI_API_KEY"] # Azure OpenAI API key
AZURE_OPENAI_MODEL_DEPLOYMENT_NAME = os.environ["AZURE_OPENAI_MODEL_DEPLOYMENT_NAME"] # Azure OpenAI model deployment name
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/custom_say_command.py b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/custom_say_command.py
deleted file mode 100644
index 176218c1fa2..00000000000
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/custom_say_command.py
+++ /dev/null
@@ -1,105 +0,0 @@
-import json
-from botbuilder.core import MessageFactory, TurnContext
-from botframework.connector import Channels
-from teams.ai.citations import AIEntity, ClientCitation, Appearance
-from teams.utils import snippet
-from teams.utils.citations import format_citations_response, get_used_citations
-from typing import Dict, Any
-
-async def say_command(context: TurnContext, state: Any, data: Dict[str, Any], feedback_loop_enabled: bool = False) -> str:
- """
- Executes the say command, sending a message to the user with the AI's response.
-
- Args:
- context (TurnContext): The turn context.
- state (Any): The turn state.
- data (Dict[str, Any]): The data containing the AI's response.
- feedback_loop_enabled (bool): Whether the feedback loop is enabled.
-
- Returns:
- str: An empty string.
- """
- # Early return if there's no valid response data to process
- if not data or not data.response or not data.response.content:
- return ""
-
- # Determine if we're in Teams channel for special formatting
- is_teams_channel = context.activity.channel_id == Channels.ms_teams
- content = ""
- result = None
-
- try:
- # Attempt to parse the response as JSON
- result = json.loads(data.response.content)
- except json.JSONDecodeError as error:
- # If not valid JSON, fallback to sending raw text with AI metadata
- print(f"Response is not valid JSON, sending the raw text. error: {error}")
- message = MessageFactory.text(data.response.content)
- # Add identify this as AI-generated content
- message.entities = [
- AIEntity(
- citation=None,
- additional_type=["AIGeneratedContent"]
- )
- ]
- # Add Teams-specific channel data if needed
- if is_teams_channel:
- message.channel_data = {"feedbackLoopEnabled": feedback_loop_enabled}
- await context.send_activity(message)
- return ""
-
- # Initialize citations tracking for the response
- citations = []
- position = 1
-
- # Process JSON result with citations if available
- if result and result.get("results") and len(result["results"]) > 0:
- for content_item in result["results"]:
- if content_item.get("citationTitle") and len(content_item["citationTitle"]) > 0:
- # Create citation object with metadata for the content source
- client_citation = ClientCitation(
- position = position,
- appearance = Appearance(
- name = content_item.get("citationTitle") or f"Document #{position}",
- url = content_item.get("citationUrl"),
- # Create a snippet/preview of the citation content, limited to 400 chars(limitation check 480)
- abstract = snippet(content_item.get("citationContent"), 400),
- )
- )
- # Add citation reference number to the answer text
- content += f"{content_item.get('answer')}[{position}]
"
- position += 1
- citations.append(client_citation)
- else:
- # Add answer without citation if no citation is available
- content += f"{content_item.get('answer')}
"
- else:
- # Use raw content if no structured results are available
- content = data.response.content
-
- # Replace newlines with HTML breaks for Teams rendering
- if is_teams_channel:
- content = content.replace("\n", "
")
-
- # Format the content with proper citation handling
- content_text = format_citations_response(content) if len(citations) < 1 else content
- # Get only the citations that are actually referenced in the text
- referenced_citations = get_used_citations(content_text, citations) if len(citations) > 0 else None
-
- # Create the message with properly formatted text
- message = MessageFactory.text(content_text)
- # Add AI entity with citation metadata
- message.entities = [
- AIEntity(
- citation=(referenced_citations if referenced_citations else []),
- additional_type=["AIGeneratedContent"],
- ),
- ]
-
- # Add Teams-specific channel data if needed
- if is_teams_channel:
- message.channel_data = {"feedbackLoopEnabled": feedback_loop_enabled}
-
- # Send the formatted message to the user
- await context.send_activity(message)
- return ""
\ No newline at end of file
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/indexers/get_data.py b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/indexers/get_data.py
index a55656e715e..bcdc9b6f93e 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/indexers/get_data.py
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/indexers/get_data.py
@@ -1,13 +1,13 @@
import os
-async def get_doc_data(embeddings):
+async def get_doc_data(embeddings, model):
with open(f'{os.getcwd()}/src/indexers/data/Contoso_Electronics_PerkPlus_Program.md', 'r') as file:
raw_description1 = file.read()
doc1 = {
"docId": "1",
"docTitle": "Contoso_Electronics_PerkPlus_Program",
"description": raw_description1,
- "descriptionVector": await get_embedding_vector(raw_description1, embeddings=embeddings),
+ "descriptionVector": get_embedding_vector(raw_description1, embeddings=embeddings, model=model),
}
with open(f'{os.getcwd()}/src/indexers/data/Contoso_Electronics_Company_Overview.md', 'r') as file:
@@ -16,7 +16,7 @@ async def get_doc_data(embeddings):
"docId": "2",
"docTitle": "Contoso_Electronics_Company_Overview",
"description": raw_description2,
- "descriptionVector": await get_embedding_vector(raw_description2, embeddings=embeddings),
+ "descriptionVector": get_embedding_vector(raw_description2, embeddings=embeddings, model=model),
}
with open(f'{os.getcwd()}/src/indexers/data/Contoso_Electronics_Plan_Benefits.md', 'r') as file:
@@ -25,17 +25,18 @@ async def get_doc_data(embeddings):
"docId": "3",
"docTitle": "Contoso_Electronics_Plan_Benefits",
"description": raw_description3,
- "descriptionVector": await get_embedding_vector(raw_description3, embeddings=embeddings),
+ "descriptionVector": get_embedding_vector(raw_description3, embeddings=embeddings, model=model),
}
return [doc1, doc2, doc3]
-async def get_embedding_vector(text: str, embeddings):
- result = await embeddings.create_embeddings(text)
- if (result.status != 'success' or not result.output):
- if result.status == 'error':
- raise Exception(f"Failed to generate embeddings for description: <{text[:200]+'...'}>\n\nError: {result.output}")
- raise Exception(f"Failed to generate embeddings for description: <{text[:200]+'...'}>")
-
- return result.output[0]
\ No newline at end of file
+def get_embedding_vector(text: str, embeddings, model):
+ try:
+ response = embeddings.embeddings.create(
+ input=text,
+ model=model
+ )
+ return response.data[0].embedding
+ except Exception as e:
+ raise Exception(f"Failed to generate embeddings for description: <{text[:200]+'...'}>\n\nError: {str(e)}")
\ No newline at end of file
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/indexers/setup.py.tpl b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/indexers/setup.py.tpl
index 68f6abae09e..2aac263304e 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/indexers/setup.py.tpl
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/indexers/setup.py.tpl
@@ -12,17 +12,16 @@ from azure.search.documents.indexes.models import (
SearchableField,
SearchField,
SearchFieldDataType,
- ComplexField,
CorsOptions,
VectorSearch,
VectorSearchProfile,
HnswAlgorithmConfiguration
)
{{#useAzureOpenAI}}
-from teams.ai.embeddings import AzureOpenAIEmbeddings, AzureOpenAIEmbeddingsOptions
+from openai import AzureOpenAI
{{/useAzureOpenAI}}
{{#useOpenAI}}
-from teams.ai.embeddings import OpenAIEmbeddings, OpenAIEmbeddingsOptions
+from openai import OpenAI
{{/useOpenAI}}
from get_data import get_doc_data
@@ -86,19 +85,18 @@ async def setup(search_api_key, search_api_endpoint, args):
search_client = SearchClient(search_api_endpoint, index, credentials)
{{#useAzureOpenAI}}
- embeddings = AzureOpenAIEmbeddings(AzureOpenAIEmbeddingsOptions(
- azure_api_key=args.api_key,
+ embeddings = AzureOpenAI(
+ api_key=args.api_key,
azure_endpoint=os.getenv('AZURE_OPENAI_ENDPOINT'),
- azure_deployment=os.getenv('AZURE_OPENAI_EMBEDDING_DEPLOYMENT')
- ))
+ api_version="2024-02-01"
+ )
+ embedding_model = os.getenv('AZURE_OPENAI_EMBEDDING_DEPLOYMENT')
{{/useAzureOpenAI}}
{{#useOpenAI}}
- embeddings=OpenAIEmbeddings(OpenAIEmbeddingsOptions(
- api_key=args.api_key,
- model='text-embedding-ada-002'
- ))
+ embedding_model='text-embedding-ada-002'
+ embeddings=OpenAI(api_key=args.api_key, model='text-embedding-ada-002')
{{/useOpenAI}}
- data = await get_doc_data(embeddings=embeddings)
+ data = await get_doc_data(embeddings=embeddings, model=embedding_model)
await upsert_documents(search_client, data)
print("Upload new documents succeeded. If they do not exist, wait for several seconds...")
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/prompts/chat/skprompt.txt b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/instructions.txt
similarity index 87%
rename from templates/vsc/python/custom-copilot-rag-azure-ai-search/src/prompts/chat/skprompt.txt
rename to templates/vsc/python/custom-copilot-rag-azure-ai-search/src/instructions.txt
index f7fe7d7a979..89f6f143d89 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/prompts/chat/skprompt.txt
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/instructions.txt
@@ -2,10 +2,8 @@ The following is a conversation with an AI assistant, who is an expert on answer
Responses should be in a short journalistic style with no more than 80 words, and provide citations.
Use the context provided in the `` tags as the source for your answers.
Response should be a json array, list all the answers and citations.
-If citationTitle is not empty, citationContent mustn't be empty!!!
-# citationTitle must be a file name!!!
If the answer no citation, set the citationTitle and citationContent as empty.
-Data format:
+Response should be a JSON object
{
"results":[
{
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/prompts/chat/config.json b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/prompts/chat/config.json
deleted file mode 100644
index 1f3e7a7e0d0..00000000000
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/prompts/chat/config.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "schema": 1.1,
- "description": "Chat with Teams RAG",
- "type": "completion",
- "completion": {
- "completion_type": "chat",
- "include_history": true,
- "include_input": true,
- "max_input_tokens": 4096,
- "max_tokens": 1000,
- "temperature": 0.9,
- "top_p": 0.0,
- "presence_penalty": 0.6,
- "frequency_penalty": 0.0,
- "stop_sequences": []
- },
- "augmentation": {
- "augmentation_type": "none",
- "data_sources": {
- "azure-ai-search": 2500
- }
- }
-}
\ No newline at end of file
diff --git a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/requirements.txt b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/requirements.txt
index 9427b4c4b4b..f1e59f638b5 100644
--- a/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/requirements.txt
+++ b/templates/vsc/python/custom-copilot-rag-azure-ai-search/src/requirements.txt
@@ -1,5 +1,9 @@
-python-dotenv
-aiohttp
+azure-identity>=1.12.0
azure-search
azure-search-documents
-teams-ai>=1.6.0,<2.0.0
\ No newline at end of file
+six
+dotenv>=0.9.9
+microsoft-teams-apps>=2.0.0a2,<3.0.0
+microsoft.teams.ai>=2.0.0a2,<3.0.0
+microsoft.teams.openai>=2.0.0a2,<3.0.0
+