创建基于 API 的消息扩展

注意

基于 API 的消息扩展仅支持搜索命令。

基于 API 的消息扩展是一种Microsoft Teams 应用功能,可将外部 API 直接集成到 Teams 中,从而增强应用的可用性并提供无缝的用户体验。 基于 API 的消息扩展支持搜索命令,可用于从 Teams 中的外部服务提取和显示数据,通过减少在应用程序之间切换的需要来简化工作流。

在开始之前,请确保满足以下要求:


1. OpenAPI 说明 (OAD)

确保遵循以下有关 OpenAPI 说明 (OAD) 文档的准则:

  • 支持 OpenAPI 版本 2.0 和 3.0.x。
  • JSON 和 YAML 是支持的格式。
  • 请求正文(如果存在)必须为 application/Json。
  • servers.url 属性定义 HTTPS 协议服务器 URL。
  • 仅支持 POST 和 GET HTTP 方法。
  • OpenAPI 说明文档必须具有 operationId
  • 仅允许一个不带默认值的必需参数。
  • 具有默认值的必需参数被视为可选参数。
  • 用户不得为标头或 Cookie 输入参数。
  • 作不得具有不带默认值的必需标头或 Cookie 参数。
  • 确保 OpenAPI 说明文档中没有远程引用。
  • 不支持为请求构造数组;但是,支持 JSON 请求正文中的嵌套对象。
  • Teams 不支持 oneOf、、 anyOfallOfnot (swagger.io) 构造。

以下代码是 OpenAPI 说明文档的示例:

openapi: 3.0.1
info:
title: OpenTools Plugin
description: A plugin that allows the user to find the most appropriate AI tools for their use cases, with their pricing information.
version: 'v1'
servers:
- url: https://gptplugin.opentools.ai
paths:
/tools:
 get:
   operationId: searchTools
   summary: Search for AI Tools
   parameters:
     - in: query
       name: search
       required: true
       schema:
         type: string
       description: Used to search for AI tools by their category based on the keywords. For example, ?search="tool to create music" will give tools that can create music.
   responses:
     "200":
       description: OK
       content:
         application/json:
           schema:
             $ref: '#/components/schemas/searchToolsResponse'
     "400":
       description: Search Error
       content:
         application/json:
           schema:
             $ref: '#/components/schemas/searchToolsError'
components:
schemas:
 searchToolsResponse:
   required:
     - search
   type: object
   properties:
     tools:
       type: array
       items:
         type: object
         properties:
           name:
             type: string
             description: The name of the tool.
           opentools_url:
             type: string
             description: The URL to access the tool.
           main_summary:
             type: string
             description: A summary of what the tool is.
           pricing_summary:
             type: string
             description: A summary of the pricing of the tool.
           categories:
             type: array
             items:
               type: string
             description: The categories assigned to the tool.
           platforms:
             type: array
             items:
               type: string
             description: The platforms that this tool is available on.
       description: The list of AI tools.
 searchToolsError:
   type: object
   properties:
     message:
       type: string
       description: Message of the error.

有关详细信息,请参阅 OpenAPI 结构。


2.应用清单

确保遵循以下应用清单指南:

  • 将应用清单版本设置为 1.17

  • 设置为 composeExtensions.composeExtensionTypeapiBased

  • 定义为 composeExtensions.apiSpecificationFile 文件夹中 OpenAPI 说明文件的相对路径。 这会将应用清单链接到 API 规范。

  • 定义为 apiResponseRenderingTemplateFile 响应呈现模板的相对路径。 这将指定用于呈现 API 响应的模板的位置。

  • 每个命令都必须具有指向响应呈现模板的链接。 这会将每个命令连接到其相应的响应格式。

  • Commands.id应用清单中的 属性必须与 OpenAPI 说明中的 匹配operationId

  • 如果所需的参数没有默认值,则应用清单中的命令 parameters.name 必须与 OpenAPI 说明文档中的 匹配 parameters.name

  • 如果没有必需的参数,则应用清单中的命令 parameters.name 必须与 OpenAPI 说明中的 可选 parameters.name 匹配。

  • 确保每个命令的参数与 OpenAPI 规范中为作定义的参数名称完全匹配。

  • 必须为每个命令定义 响应呈现模板 ,该命令用于转换来自 API 的响应。

  • 完整说明不得超过 128 个字符。

    {
    "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.17/MicrosoftTeams.schema.json",
    +  "manifestVersion": "1.17",
    "version": "1.0.0",
    "id": "04805b4b-xxxx-xxxx-xxxx-4dbc1cac8f89",
    "packageName": "com.microsoft.teams.extension",
    "developer": {
        "name": "Teams App, Inc.",
        "websiteUrl": "https://www.example.com",
        "privacyUrl": "https://www.example.com/termofuse",
        "termsOfUseUrl": "https://www.example.com/privacy"
    },
    "icons": {
        "color": "color.png",
        "outline": "outline.png"
    },
    "name": {
        "short": "AI tools",
        "full": "AI tools"
    },
    "description": {
        "short": "AI tools",
        "full": "AI tools"
    },
    "accentColor": "#FFFFFF",
    "composeExtensions": [
        {
    +      "composeExtensionType": "apiBased",
    +      "authorization": {
    +        "authType": "apiSecretServiceAuth ",
    +        "apiSecretServiceAuthConfiguration": {
    +            "apiSecretRegistrationId": "9xxxxxxx-7xxx-4xxx-bxxx-1xxxxxxxxxxx"
    +        }
    +      },
    +      "apiSpecificationFile": "aitools-openapi.yml",
           "commands": [
           {
              "id": "searchTools",
              "type": "query",
              "context": [
                 "compose",
                 "commandBox"
              ],
              "title": "search for AI tools",
              "description": "search for AI tools",
              "parameters": [
                 {
                 "name": "search",
                 "title": "search query",
                 "description": "e.g. search='tool to create music'"
                 }
              ],
    +          "apiResponseRenderingTemplateFile": "response-template.json"
           }
           ]
        }
    ],
    "validDomains": []
    }
    

参数

名称 说明
composeExtensions.composeExtensionType Compose扩展类型。 将值更新为 apiBased
composeExtensions.authorization 基于 API 的消息扩展的授权相关信息
composeExtensions.authorization.authType 可能授权类型的枚举。 支持的值为 noneapiSecretServiceAuthmicrosoftEntra
composeExtensions.authorization.apiSecretServiceAuthConfiguration 对象捕获执行服务身份验证所需的详细信息。仅当身份验证类型为 apiSecretServiceAuth时适用。
composeExtensions.authorization.apiSecretServiceAuthConfiguration.apiSecretRegistrationId 开发人员通过开发人员门户提交 API 密钥时返回的注册 ID。
composeExtensions.apiSpecificationFile 引用应用包中的 OpenAPI 说明文件。 类型为 apiBased时包括 。
composeExtensions.commands.id 分配给搜索命令的唯一 ID。 用户请求包含此 ID。 ID 必须与 OpenAPI 说明中提供的 匹配 OperationId
composeExtensions.commands.context 定义消息扩展入口点的数组。 默认值为 composecommandBox
composeExtensions.commands.parameters 定义命令的参数的静态列表。 名称必须映射到 parameters.name OpenAPI 说明中的 。 如果要引用请求正文架构中的属性,则该名称必须映射到 properties.name 或 查询参数。
composeExtensions.commands.apiResponseRenderingTemplateFile 用于格式化从开发人员 API 到自适应卡片响应的 JSON 响应的模板。 [必需]

有关详细信息,请参阅 composeExtensions


3. 响应呈现模板
  • 在 属性中$schema定义架构引用 URL,以建立模板的结构。
  • 支持的值responseLayoutgridlist ,确定响应的视觉呈现方式。
  • jsonPath自适应卡片的数据不是根对象时,建议对数组使用 。 例如,如果数据嵌套在 下 productDetails,则 JSON 路径为 productDetails
  • 定义为 jsonPath API 响应中相关数据或数组的路径。 如果路径指向数组,则数组中的每个条目都与自适应卡片模板绑定,并作为单独的结果返回。 [可选]
  • 获取 用于验证响应呈现模板的示例响应。 这可用作一个测试,以确保模板按预期工作。
  • 使用 Fiddler 或 Postman 等工具 调用 API 并确保请求和响应有效。 此步骤对于排查和确认 API 正常运行至关重要。
  • 可以使用自适应卡片Designer将 API 响应绑定到响应呈现模板并预览自适应卡片。 在 卡片有效负载编辑器 中插入模板,并在示例 数据编辑器中插入示例响应条目。

以下代码是响应呈现模板的示例:

响应呈现模板示例
{
"version": "1.0",
"jsonPath": "repairs",
"responseLayout": "grid",
"responseCardTemplate": {
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "type": "AdaptiveCard",
  "version": "1.4",
  "body": [
    {
      "type": "Container",
      "items": [
        {
          "type": "ColumnSet",
          "columns": [
            {
              "type": "Column",
              "width": "stretch",
              "items": [
                {
                  "type": "TextBlock",
                  "text": "Title: ${if(title, title, 'N/A')}",
                  "wrap": true
                },
                {
                  "type": "TextBlock",
                  "text": "Description: ${if(description, description, 'N/A')}",
                  "wrap": true
                },
                {
                  "type": "TextBlock",
                  "text": "Assigned To: ${if(assignedTo, assignedTo, 'N/A')}",
                  "wrap": true
                },
                {
                  "type": "Image",
                  "url": "${image}",
                  "size": "Medium",
                  "$when": "${image != null}"
                }
              ]
            },
            {
              "type": "Column",
              "width": "auto",
              "items": [
                {
                  "type": "Image",
                  "url": "${if(image, image, '')}",
                  "size": "Medium"
                }
              ]
            }
          ]
        },
        {
          "type": "FactSet",
          "facts": [
            {
              "title": "Repair ID:",
              "value": "${if(id, id, 'N/A')}"
            },
            {
              "title": "Date:",
              "value": "${if(date, date, 'N/A')}"
            }
          ]
        }
      ]
    }
  ]
  },
  "previewCardTemplate": {
  "title": "Title: ${if(title, title, 'N/A')}",
  "subtitle": "Description: ${if(description, description, 'N/A')}",
  "text": "Assigned To: ${if(assignedTo, assignedTo, 'N/A')}",
  "image": {
    "url": "${image}",
    "$when": "${image != null}"
    }
  }
 }

预览卡

屏幕截图显示了在搜索特定单词时显示预览卡片数组的 compose 扩展的示例。在这种情况下,在“SME 测试应用”中搜索“a”将返回五张卡片,其中显示“Title”、“Description” (截断) 和“AssignedTo”属性和值。

扩展的自适应卡片

用户选择预览版卡后自适应卡片的外观示例。自适应卡片显示 Title、完整的 Description、AssignedTo、RepairId 和 Date 值。

参数

属性 类型 说明 必需
version string 当前响应呈现模板的架构版本。
jsonPath string responseCardTemplate 和 previewCardTemplate 应应用到的结果中相关节的路径。 如果未设置,则根对象被视为相关部分。 如果相关部分是数组,则每个条目将映射到 responseCardTemplate 和 previewCardTemplate。
responseLayout responseLayoutType 指定消息扩展浮出控件中结果的布局。 支持的类型为 listgrid
responseCardTemplate adaptiveCardTemplate 用于从结果条目创建自适应卡片的模板。
previewCardTemplate previewCardTemplate 用于从结果条目创建预览卡的模板。 生成的预览卡显示在消息扩展浮出控件菜单中。

JSON 路径

JSON 路径是可选的,但可用于数组,或者用作自适应卡片数据的对象不是根对象。 JSON 路径必须遵循 Newtonsoft 定义的格式。 如果 JSON 路径指向数组,则该数组中的每个条目都与自适应卡片模板绑定,并返回为单独的结果。

假设你具有以下产品列表的 JSON,并且你希望为每个条目创建卡结果。

{
   "version": "1.0",
   "title": "All Products",
   "warehouse": {
      "products": [
        ...
      ]
   }
}

可以看到,结果数组位于“products”下,嵌套在“warehouse”下,因此 JSON 路径为“warehouse.products”。

通过将模板插入卡片有效负载编辑器,使用自适应卡片Designer预览自适应卡片。 从数组或 对象获取示例响应条目,并将其插入示例数据编辑器中。 确保卡正确呈现,并且是你喜欢的。

架构映射

OpenAPI 说明文档中的属性映射到自适应卡片模板,如下所示:

  • stringnumberinteger类型 boolean 将转换为 TextBlock。

    • 源架构stringnumberintegerboolean

       name:
         type: string
         example: doggie
      
    • 目标架构Textblock

      {
      "type": "TextBlock",
      "text": "name: ${if(name, name, 'N/A')}",
      "wrap": true
      }
      
  • array:数组转换为自适应卡内的容器。

    • 源架构array

          type: array
                    items:
                    required:
                      - name
                    type: object
                      properties:
                      id:
                        type: integer
                      category:
                        type: object
                        properties:
                        name:
                          type: string
      
    • 目标架构Container

          {
                    "type": "Container",
                    "$data": "${$root}",
                    "items": [
                      {
                        "type": "TextBlock",
                        "text": "id: ${if(id, id, 'N/A')}",
                        "wrap": true
                      },
                      {
                        "type": "TextBlock",
                        "text": "category.name: ${if(category.name, category.name, 'N/A')}",
                        "wrap": true
                      }
                    ]
                  }
      
      
  • object:对象转换为自适应卡片中的嵌套属性。

    • 源架构object

      components:
        schemas:
          Pet:
              category:
                type: object
              properties:
                id:
                  type: integer
                name:
                  type: string
      
      
    • 目标架构:自适应卡片中的嵌套属性

      {
        "type": "TextBlock",
        "text": "category.id: ${if(category.id, category.id, 'N/A')}",
        "wrap": true
      },
      {
        "type": "TextBlock",
        "text": "category.name: ${if(category.name, category.name, 'N/A')}",
        "wrap": true
      }
      
      
  • image:如果属性是图像 URL,则它将转换为自适应卡片中的 Image 元素。

    • 源架构image

          image:
            type: string
            format: uri
            description: The URL of the image of the item to be repaired
      
      
    • 目标架构"Image"

      {
            "type": "Image",
            "url": "${image}",
            "$when": "${image != null}"
          }
      
      

可以使用开发人员门户 for Teams 创建基于 API 的消息扩展,Microsoft 365 代理工具包 (以前称为 Teams Toolkit) for Visual Studio Code、命令行接口 (CLI) 或 Visual Studio。

若要使用开发人员门户创建基于 API 的消息扩展,请执行以下步骤:

  1. 转到 开发人员门户

  2. 转到 “应用”。

  3. 选择“ + 新建应用”。

  4. 输入应用的名称,并选择 “清单版本 ”作为 “公共开发人员预览版” (devPreview)

  5. 选择“添加”。

    屏幕截图显示了在开发人员门户中选择为“最新预发行 (devPreview) ”的应用名称和清单版本。

  6. 在左窗格中的 “配置”下,更新以下 “基本信息”:

    1. 全名
    2. 简短说明
    3. 较长说明
    4. 开发人员或公司名称
    5. 网站 (必须是有效的 HTTPS URL)
    6. 隐私策略
    7. 使用条款
  7. 选择“保存”

  8. 选择“ 应用功能”。

  9. 选择 “消息扩展”。

    屏幕截图显示了开发人员门户中的消息扩展选项。

  10. “消息扩展类型”下,选择“ API”。

    1. 如果收到一个免责声明,指出 机器人消息扩展已被用户使用。是否要将消息扩展类型更改为 API?,请选择“ 是,更改”。
  11. “OpenAPI 规范”下,选择“ 立即上传”。

    屏幕截图显示了开发人员门户中的“立即上传”选项。

  12. 选择 JSON 或 YAML 格式的 OpenAPI 说明文档,然后选择“ 打开”。

  13. 选择“保存”。 此时会显示一个弹出窗口,其中包含 已成功保存的消息 API 规范

  14. 选择“ 已获取”。

    屏幕截图显示了 API 规范已成功保存消息和“已获取”按钮的示例。

添加命令

注意

从 API 生成的消息扩展仅支持单个参数。

可以将命令和参数添加到消息扩展,以添加命令:

  1. “消息扩展类型”下,选择“ 添加”。

    屏幕截图显示了用于在开发人员门户中添加命令的添加选项。

    此时将显示 “添加命令 ”弹出窗口,其中包含 OpenAPI 说明文档中所有可用 API 的列表。

  2. 从列表中选择一个 API,然后选择“ 下一步”。

    屏幕截图显示了“添加命令”弹出窗口中 OpenAPI 说明文档中的 API 列表。

  3. “响应模板”下,选择“ 立即上传”。

    屏幕截图显示“立即上传”选项,用于为命令添加自适应卡片模板。

    注意

    如果有多个 API,请确保为每个 API 上传自适应卡片响应模板。

  4. 选择 JSON 格式的自适应卡片响应模板文件,然后选择 “打开”。

    从自适应卡片模板自动更新以下属性:

    • 命令类型
    • 命令 ID
    • 命令标题
    • 参数名称
    • 参数说明
  5. “详细信息”下,更新 命令说明

  6. 如果要使用 智能 Microsoft 365 Copilot 副驾驶® 中的触发器启动命令,请打开“当用户打开扩展时自动运行此命令”开关。

  7. 选择“添加”。 已成功添加命令。

    屏幕截图显示了命令详细信息页中可用的字段。

  8. 选择“保存”

  9. “身份验证和授权”下,选择以下任一选项:

    • 不建议使用身份验证 ()
    • API 密钥
    • OAuth

创建基于 API 的消息扩展。

屏幕截图显示在开发人员门户的应用功能页中创建的智能 Microsoft 365 Copilot 副驾驶®插件。

若要测试在开发人员门户中创建的基于 API 的消息扩展,可以使用以下方法:

  • 在 Teams 中预览:打开邮件扩展,然后选择右上角的“ 在 Teams 中预览 ”。 你将重定向到 Teams,可在其中将应用添加到 Teams 以预览应用。

  • 下载应用包:在消息扩展页上,从左窗格中选择 “应用包 ”,然后在窗口左上角选择“ 下载应用包”。 应用包在 .zip 文件中下载到本地计算机。 可以将应用包上传到团队并测试消息扩展。

多个参数

多个参数允许基于 API 的消息扩展具有多个查询命令的输入类型。 例如,可以按流派、分级、状态和日期搜索动漫。

可以为清单中的参数指定输入类型、标题、说明和必填字段。

  • 参数 isRequired 字段中的 属性指示查询命令是否需要参数。
  • name应用清单中 字段的 parameters 属性必须与 OpenAPI 说明文档中对应参数的 字段匹配id

示例

"composeExtensions": [
        {
            "composeExtensionType": "apiBased",
            "apiSpecificationFile": "apiSpecificationFiles/openapi.json",
            "commands": [
                {
                    "context": [
                        "compose"
                    ],
                    "type": "query",
                    "title": "Search Animes",
                    "id": "getAnimeSearch",
                    "parameters": [
                        {
                            "name": "q",
                            "title": "Search Query",
                            "description": "The search query",
                            "isRequired": true
                        },
                        {
                            "name": "type",
                            "inputType": "choiceset",
                            "title": "Type",
                            "description": "Available anime types",
                            "choices": [
                                {
                                    "title": "TV",
                                    "value": "tv"
                                },
                                {
                                    "title": "OVA",
                                    "value": "ova"
                                },
                                {
                                    "title": "Movie",
                                    "value": "movie"
                                },
                                {
                                    "title": "Special",
                                    "value": "special"
                                },
                                {
                                    "title": "ONA",
                                    "value": "ona"
                                },
                                {
                                    "title": "Music",
                                    "value": "music"
                                }
                            ]
                        },
                        {
                            "name": "status",
                            "inputType": "choiceset",
                            "title": "Status",
                            "description": "Available airing statuses",
                            "choices": [
                                {
                                    "title": "Airing",
                                    "value": "airing"
                                },
                                {
                                    "title": "Completed",
                                    "value": "complete"
                                },
                                {
                                    "title": "Upcoming",
                                    "value": "upcoming"
                                }
                            ]
                        },
                        {
                            "name": "rating",
                            "inputType": "choiceset",
                            "title": "Rating",
                            "description": "Available ratings",
                            "choices": [
                                {
                                    "title": "G",
                                    "value": "g"
                                },
                                {
                                    "title": "PG",
                                    "value": "pg"
                                },
                                {
                                    "title": "PG-13",
                                    "value": "pg13"
                                },
                                {
                                    "title": "R",
                                    "value": "r17"
                                },
                                {
                                    "title": "R+",
                                    "value": "r"
                                },
                                {
                                    "title": "Rx",
                                    "value": "rx"
                                }
                            ]
                        }
                    ],
                    "description": "Search animes",
                    "apiResponseRenderingTemplateFile": "response_json/getAnimeSearch.json"
                },
                {
                    "context": [
                        "compose"
                    ],
                    "type": "query",
                    "title": "Search mangas",
                    "id": "getMangaSearch",
                    "parameters": [
                        {
                            "name": "q",
                            "title": "Search Query",
                            "description": "The search query",
                            "isRequired": true
                        },
                        {
                            "name": "type",
                            "inputType": "choiceset",
                            "title": "Type",
                            "description": "Available manga types",
                            "choices": [
                                {
                                    "title": "Manga",
                                    "value": "manga"
                                },
                                {
                                    "title": "Novel",
                                    "value": "novel"
                                },
                                {
                                    "title": "Light Novel",
                                    "value": "lightnovel"
                                },
                                {
                                    "title": "One Shot",
                                    "value": "oneshot"
                                },
                                {
                                    "title": "Doujin",
                                    "value": "doujin"
                                },
                                {
                                    "title": "Manhwa",
                                    "value": "manhwa"
                                },
                                {
                                    "title": "Manhua",
                                    "value": "manhua"
                                }
                            ]
                        },
                        {
                            "name": "status",
                            "inputType": "choiceset",
                            "title": "Status",
                            "description": "Available manga statuses",
                            "choices": [
                                {
                                    "title": "Publishing",
                                    "value": "publishing"
                                },
                                {
                                    "title": "Complete",
                                    "value": "complete"
                                },
                                {
                                    "title": "Hiatus",
                                    "value": "hiatus"
                                },
                                {
                                    "title": "Discontinued",
                                    "value": "discontinued"
                                },
                                {
                                    "title": "Upcoming",
                                    "value": "upcoming"
                                }
                            ]
                        },
                        {
                            "name": "start_date",
                            "title": "Start Date",
                            "description": "Start date of the manga",
                            "inputType": "date"
                        },
                        {
                            "name": "end_date",
                            "title": "End Date",
                            "description": "End date of the manga",
                            "inputType": "date"
                        }
                    ],

分步指南

若要生成基于 API 的消息扩展,请遵循以下分步指南:

教程:生成基于 API 的消息扩展

注意

基于 API 的消息扩展仅支持搜索命令。

使用 API 构建的消息扩展 (基于 API 的) 通过允许 Teams 应用与外部服务交互来显著增强其功能。 基于 API 的消息扩展可以减少在不同应用程序之间切换的需要,从而帮助简化工作流。

可以使用基于 API 的消息扩展来集成业务工作流中常用的外部服务。 例如,经常使用 CRM 系统进行客户管理的企业可以使用消息扩展直接从 Teams 提取和显示客户数据。 此应用可减少在不同应用程序之间切换的需要,从而节省时间并提高效率。 Teams 提供的所有平台(包括桌面、Web 和移动版)都支持此功能。

先决条件

下面是生成和部署应用所需的工具列表。

安装 用于使用...
Microsoft Teams Microsoft Teams,通过聊天、会议或通话应用与你合作的每个人进行协作 - 所有这些都在一个位置。
Microsoft Edge(推荐)或 Google Chrome 包含开发人员工具的浏览器。
Visual Studio Code JavaScript、TypeScript 或 SharePoint 框架 (SPFx) 生成环境。 使用版本 1.55 或更高版本。
Microsoft 365 开发人员帐户 具有安装应用的相应权限的 Teams 帐户的访问权限。
Azure帐户 访问Azure资源。
OpenAPI 说明 (OAD) 文档 介绍 API 功能的文档。 有关详细信息,请参阅 OpenAPI 说明

设置 Teams 开发租户

租户类似于 Teams 中组织的空间或容器,可在其中聊天、共享文件和运行会议。 此空间也是上传和测试自定义应用的位置。 让我们验证是否已准备好使用租户进行开发。

检查自定义应用上传选项

创建应用后,必须在 Teams 中加载应用,而无需分发它。 此过程称为自定义应用上传。 登录到 Microsoft 365 帐户以查看此选项。

注意

自定义应用上传对于在 Teams 本地环境中预览和测试应用是必需的。 如果未启用,则无法在 Teams 本地环境中预览和测试应用。

是否已拥有租户,并且是否具有管理员访问权限? 我们来检查,如果你真的这样做了!

验证是否可以在 Teams 中上传自定义应用:

  1. 在 Teams 客户端中,选择“ 应用” 图标。

  2. 选择“管理应用”。

  3. 选择 “上传应用”。

  4. 查找“ 上传自定义应用”选项。 如果看到 选项,则表示已启用自定义应用上传。

    屏幕截图显示了机器人主页。

注意

如果找不到上传自定义应用的选项,请与 Teams 管理员联系。

创建免费的 Teams 开发人员租户 (可选)

如果没有 Teams 开发人员帐户,可以免费获取它。 加入 Microsoft 365 开发人员计划!

  1. 转到 Microsoft 365 开发人员计划

  2. 选择“ 立即加入 ”,然后按照屏幕上的说明进行作。

  3. 在欢迎屏幕中,选择“ 设置 E5 订阅”。

  4. 设置管理员帐户。 完成后,将显示以下屏幕。

    屏幕截图显示了 Microsoft 365 开发人员计划。

  5. 使用刚刚设置的管理员帐户登录到 Teams。 验证在 Teams 中是否具有 “上传自定义应用 ”选项。

获取免费Azure帐户

如果要在 Azure 中托管应用或访问资源,则必须拥有Azure订阅。 在开始之前创建一个免费帐户

你拥有设置帐户的所有工具。 接下来,让我们设置开发环境并开始生成! 选择要首先生成的应用。

创建 OpenAPI 说明文档

OpenAPI 说明

OpenAPI 说明 (OAD) 是行业标准规范,概述了 OpenAPI 文件的结构和概述方式。 它是一种与语言无关、可读的格式,用于描述 API。 人类和机器都很容易读取和写入。 该架构是计算机可读的,以 YAML 或 JSON 表示。

若要与 API 交互,需要一个 OpenAPI 说明文档。 OpenAPI 说明文档必须满足以下条件:

  • auth不能指定 属性。
  • JSON 和 YAML 是支持的格式。
  • 支持 OpenAPI 版本 2.0 和 3.0.x。
  • Teams 不支持 oneOf、anyOf、allOf,并且不支持 (swagger.io) 构造。
  • 不支持为请求构造数组,但支持 JSON 请求正文中的嵌套对象。
  • 请求正文(如果存在)必须是 application/Json,以确保与各种 API 兼容。
  • servers.url 属性定义 HTTPS 协议服务器 URL。
  • 仅支持单个参数搜索。
  • 仅允许一个不带默认值的必需参数。
  • 仅支持 POST 和 GET HTTP 方法。
  • OpenAPI 说明文档必须具有 operationId
  • 作不得要求不带默认值的标头或 Cookie 参数。
  • 命令必须正好具有一个参数。
  • 确保 OpenAPI 说明文档中没有远程引用。
  • 具有默认值的必需参数被视为可选参数。

在本教程中,我们使用以下 OpenAPI 说明作为示例:

OpenAPI 说明
openapi: 3.0.1
info:
  title: OpenTools Plugin
  description: A plugin that allows the user to find the most appropriate AI tools for their use cases, with their pricing information.
  version: 'v1'
servers:
  - url: https://gptplugin.opentools.ai
paths:
  /tools:
    get:
      operationId: searchTools
      summary: Search for AI Tools
      parameters:
        - in: query
          name: search
          required: true
          schema:
            type: string
          description: Used to search for AI tools by their category based on the keywords. For example, a search for "tool to create music" provides a list of tools that can create music.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/searchToolsResponse'
        "400":
          description: Search Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/searchToolsError'
components:
  schemas:
    searchToolsResponse:
      required:
        - search
      type: object
      properties:
        tools:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
                description: The name of the tool.
              opentools_url:
                type: string
                description: The URL to access the tool.
              main_summary:
                type: string
                description: A summary of what the tool is.
              pricing_summary:
                type: string
                description: A summary of the pricing of the tool.
              categories:
                type: array
                items:
                  type: string
                description: The categories assigned to the tool.
              platforms:
                type: array
                items:
                  type: string
                description: The platforms that this tool is available on.
          description: The list of AI tools.
    searchToolsError:
      type: object
      properties:
        message:
          type: string
          description: Message of the error.
      

注意

确保 该 required: true 属性仅可用于一个参数。 如果有多个必需参数,则可以将其他参数的必需属性更新为 required: false

可以验证 OpenAPI 说明文档是否有效。 若要验证,请执行以下步骤:

  1. 转到 Swagger/OpenAPI 验证程序 并验证 OpenAPI 说明文档。

  2. 保存 OpenAPI 说明文档。

  3. 转到 Swagger 编辑器

  4. 在左窗格中,将 OpenAPI 说明粘贴到编辑器中。

  5. 在右窗格中,选择“ GET”。

  6. 选择“ 试用”。

  7. 输入 搜索 参数的值作为 创建音乐的工具

  8. 选择“ 执行”。 swagger 编辑器显示包含产品列表的响应。

    屏幕截图显示了 swagger 编辑器中的参数、其值和 “EXECUTE” 选项。

  9. 转到 服务器响应>响应正文

  10. 在 下 products,从列表中复制第一个产品,并将其保存以供将来参考。

    屏幕截图显示从响应正文中选择的突出显示的产品。

创建响应呈现模板

OpenAPI 说明文档需要响应呈现模板,以便应用响应 GET 或 POST 请求。 响应呈现模板由自适应卡片模板、预览卡模板和元数据组成。

自适应卡片模板

若要创建自适应卡片模板,请执行以下步骤:

  1. 转到 ChatGPT ,并在消息撰写区域中询问以下查询:

    Create an Adaptive Card Template that binds to the following response:
        "categories": [
            "Music Generation",
            "AI Detection"
        ],
        "chatbot_short_url": "https://goto.opentools.ai/c/ai-music-generator",
        "main_summary": "AI Music Generator is an AI-powered music composing tool that allows users to create original and personalized music for various purposes. It can generate melodies, harmonies, and rhythms tailored to specific needs and preferences, with customization options such as genre, mood, length, and instrumentation. The tool is designed for creative individuals, from beginners to professionals, and can produce high-quality music in seconds. Every generated piece of music is royalty-free and can be used instantly, with no limitations on beat creation. With advanced AI technology, AI Music Generator makes music production accessible to everyone.",
        "name": "AI Music Generator",
        "opentools_url": "https://goto.opentools.ai/ai-music-generator",
        "platforms": [
            "Web",
            "App",
            "API"
        ]
    
  2. 选择“ 发送消息”。

  3. ChatGPT 使用绑定到示例数据的自适应卡片模板生成响应。 保存自适应卡片模板以供将来参考。

下面是自适应卡片模板的示例:

自适应卡片模板
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.4",
"body": [
    {
    "type": "TextBlock",
    "text": "AI Music Generator",
    "weight": "Bolder",
    "size": "Large"
    },
    {
    "type": "TextBlock",
    "text": "Categories",
    "size": "Medium"
    },
    {
    "type": "TextBlock",
     "text": "Music Generation, AI Detection",
     "wrap": true
    },
    {
    "type": "TextBlock",
    "text": "Description",
    "size": "Medium"
    },
    {
    "type": "TextBlock",
    "text": "AI Music Generator is an AI-powered music composing tool that allows users to create original and personalized music for various purposes. It can generate melodies, harmonies, and rhythms tailored to specific needs and preferences, with customization options such as genre, mood, length, and instrumentation. The tool is designed for creative individuals, from beginners to professionals, and can produce high-quality music in seconds. Every generated piece of music is royalty-free and can be used instantly, with no limitations on beat creation. AI Music Generator is powered by advanced AI technology, and it makes music production accessible to everyone.",
    "wrap": true
    },
    {
    "type": "TextBlock",
    "text": "Platform",
    "size": "Medium"
    },
    {
    "type": "TextBlock",
    "text": "Web, App, API",
    "wrap": true
    }
],
"actions": [
    {
    "type": "Action.OpenUrl",
    "title": "Learn More",
    "url": "https://goto.opentools.ai/ai-music-generator"
    },
    {
    "type": "Action.OpenUrl",
    "title": "Try It",
    "url": "https://goto.opentools.ai/c/ai-music-generator"
    }
]
}

  1. 若要验证生成的自适应卡片是否绑定到示例数据,请执行以下步骤:
    1. 转到自适应卡片Designer

    2. 转到 “选择主机应用”,然后从下拉列表中选择“ Microsoft Teams ”。

    3. 转到 卡片有效负载编辑器 并粘贴自适应卡片模板代码。

    4. 转到 示例数据编辑器 并粘贴之前保存的 GET API 响应。

      屏幕截图显示了自适应卡片设计器,其中包含自适应卡片模板和示例数据。

    5. 选择 “预览模式”。 自适应卡片设计器显示自适应卡片,其中包含将响应绑定到模板的数据。

      屏幕截图显示了自适应卡片设计器,其中包含自适应卡片模板和示例数据。

创建预览卡模板

预览卡模板可以包含 titlesubtitleimage 属性。 如果 API 响应没有图像,则可以删除图像属性。

下面是预览卡模板的示例:

预览卡模板
   "previewCardTemplate": {
        "title": "${if(name, name, 'N/A')}",
        "subtitle": "$${if(price, price, 'N/A')}"
    } 

titlesubtitle创建 if 条件,其中:

  • 如果存在名称,机器人将使用名称。
  • 如果名称不存在,机器人将使用 NA。

例如,"title": "Name: ${if(name, name, 'N/A')}"。 保存预览卡模板以供将来参考。

响应呈现模板

响应呈现模板必须符合 托管在 的 https://developer.microsoft.com/json-schemas/teams/v1.20/MicrosoftTeams.ResponseRenderingTemplate.schema.json架构。

若要创建响应呈现模板,请执行以下步骤:

  1. 创建 JSON 文件并将以下代码添加到该文件:

    { 
      "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.20/MicrosoftTeams.ResponseRenderingTemplate.schema.json", 
      "version": "1.0", 
      "jsonPath": "", 
      "responseLayout": "", 
      "responseCardTemplate": { 
     },
     "previewCardTemplate": {
         }
     }
    
  2. 按如下所示更新响应呈现模板中的属性:

    1. "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.20/MicrosoftTeams.ResponseRenderingTemplate.schema.json"

    2. "version": "1.0"

      version 是要使用的呈现模板的版本

    3. "jsonPath": "tools"

      jsonPath 是响应 JSON 响应中的一个或多个结果的路径。 jsonPath将 添加到 API 响应中产品列表中的相关数据/数组。 在本例中, jsonPath 为 工具。 有关如何确定 JSON 路径的详细信息,请参阅 使用 JSON 路径查询 JSON

    4. "responseLayout": "list"

      responseLayout 指定附件的布局。 用于结果类型的响应。 支持的类型是列表和网格。 如果响应正文包含具有多个元素(如文本、标题和图像)的对象,则必须将响应布局设置为 list。 如果 API 响应仅包含图像或缩略图,则必须将响应布局设置为 grid

    5. "responseCardTemplate":粘贴前面保存的自适应卡片模板代码。

      responseCardTemplate 是一个自适应卡片模板,用于将 JSON 响应映射到自适应卡片。

    6. "previewCardTemplate":粘贴之前保存的预览卡模板代码。

      previewCardTemplate是预览卡模板用于在消息扩展浮出控件中显示结果的预览。

  3. 将响应呈现模板保存在保存 OpenAPI 说明文档的同一文件夹中。

以下代码是响应呈现模板的示例:

响应呈现模板
{
    "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.20/MicrosoftTeams.ResponseRenderingTemplate.schema.json",
    "version": "1.0",
    "jsonPath": "tools",
    "responseLayout": "list",
    "responseCardTemplate": {
        "type": "AdaptiveCard",
        "version": "1.4",
        "body": [
            {
            "type": "TextBlock",
            "text": "AI Music Generator",
            "weight": "Bolder",
            "size": "Large"
            },
            {
            "type": "TextBlock",
            "text": "Categories",
            "size": "Medium"
            },
            {
            "type": "TextBlock",
            "text": "Music Generation, AI Detection",
            "wrap": true
            },
            {
            "type": "TextBlock",
            "text": "Description",
            "size": "Medium"
            },
            {
            "type": "TextBlock",
            "text": "AI Music Generator is an AI-powered music composing tool that allows users to create original and personalized music for various purposes. It can generate melodies, harmonies, and rhythms tailored to specific needs and preferences, with customization options such as genre, mood, length, and instrumentation. The tool is designed for creative individuals, from beginners to professionals, and can produce high-quality music in seconds. Every generated piece of music is royalty-free and can be used instantly, with no limitations on beat creation. With advanced AI technology, AI Music Generator makes music production accessible to everyone.",
            "wrap": true
            },
            {
            "type": "TextBlock",
            "text": "Platform",
            "size": "Medium"
            },
            {
            "type": "TextBlock",
            "text": "Web, App, API",
            "wrap": true
            }
        ],
        "actions": [
            {
            "type": "Action.OpenUrl",
            "title": "Learn More",
            "url": "https://goto.opentools.ai/ai-music-generator"
            },
            {
            "type": "Action.OpenUrl",
            "title": "Try It",
            "url": "https://goto.opentools.ai/c/ai-music-generator"
            }
        ]
    },
    "previewCardTemplate": {
        "title": "${if(name, name, 'N/A')}",
        "subtitle": "$${if(price, price, 'N/A')}"
    } 
}

创建应用清单

现在,需要 (以前称为 Teams 应用清单) 创建应用清单。 应用清单介绍了你的应用如何集成到 Microsoft Teams 产品中。

创建 Teams 应用清单

若要创建清单,请执行以下步骤:

  1. 创建新的 JSON 文件。 应用清单必须符合 应用清单架构中定义的架构的 1.20 版本。

  2. 将以下代码添加到 JSON 文件:

    应用清单
    {
     "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.20/MicrosoftTeams.schema.json",
     "manifestVersion": "1.20",
     "version": "1.0.3",
     "id": "<<YOUR-MICROSOFT-APP-ID>>",
     "packageName": "com.microsoft.teams.extension",
     "developer": {
         "name": "Teams App, Inc.",
         "websiteUrl": "https://www.example.com",
         "privacyUrl": "https://www.example.com/termofuse",
         "termsOfUseUrl": "https://www.example.com/privacy"
     },
     "icons": {
         "color": "color.png",
         "outline": "outline.png"
     },
     "name": {
         "short": "Search ME API",
         "full": "Search ME API full"
     },
     "description": {
         "short": "product app for testing API Message Extensions",
         "full": "product app for testing API Message Extensions"
     },
     "accentColor": "#FFFFFF",
     "composeExtensions": [
         {
             "composeExtensionType": "",
             "apiSpecificationFile": "",
             "commands": [
                 {
                     "context": [
                         "compose"
                     ],
                     "type": "query",
                     "title": "API for fetching Klarna.",
                     "id": "",
                     "parameters": [
                         {
                             "name": "",
                             "title": "",
                             "description": ""
                         }
                     ],
                     "description": "",
                     "apiResponseRenderingTemplateFile": ""
                 }
             ]
         }
     ],
     "permissions": [
         "identity",
         "messageTeamMembers"
     ],
     "validDomains": []
    }
    
  3. 按如下所示更新应用清单属性:

    • 将 替换为 <<YOUR-MICROSOFT-APP-ID>> 机器人Microsoft应用 ID。
    • 将 的值 composeExtensionType 更新为 apiBased
    • 将 的值 apiSpecificationFile 更新为 OpenAPI 说明文件的路径。
    • 将 的值 commands.id 更新为 searchTools
    • 将 的值 commands.title 更新为 Search for AI Tools
    • 将 的值 commands.description 更新为 Search for AI Tools
    • 将 的值 parameters.name 更新为 search。 如果没有参数,则值必须是查询参数; properties.name 如果引用请求正文架构中的属性,则这些值必须是查询参数。
    • apiResponseRenderingTemplateFile 更新为响应呈现模板文件的路径。
    • 将 的值 validDomains 更新为 service URL OpenAPI 说明文件中定义的终结点。
  4. 将 Teams 应用清单保存在保存 OpenAPI 说明文档和响应呈现模板的同一文件夹中。

    • 需要彩色图像和轮廓图像。 这些图像应包含在 文件夹中,并在 Teams 应用清单中引用。
    • 压缩文件夹的内容。 zip 文件必须包含以下文件:
      • OpenAPI 说明文档
      • 响应呈现模板
      • 应用部件清单
      • 彩色图标
      • 大纲图标

将自定义应用上传到 Teams

登录到 Teams 测试环境以在 Teams 中测试应用。 若要在 Teams 中上传自定义应用,请执行以下步骤:

  1. 转到 Microsoft Teams 并使用测试租户凭据登录。

  2. 转到 “应用>”“管理应用>”“上传应用”。

  3. 选择 “上传自定义应用”。

  4. 选择创建的 zip 文件,然后选择“ 打开”。

  5. 选择“添加”。

    消息扩展应用的屏幕截图,其中突出显示了“添加”选项。

  6. 选择 “打开”

    消息扩展应用的屏幕截图,其中突出显示了“打开”选项。

  7. 转到聊天,然后从邮件撰写区域选择 + ,然后搜索你的应用。

  8. 选择应用并进行搜索查询。

    屏幕截图显示,从聊天菜单中的加号图标,用户可以调用浮出控件菜单中显示的消息扩展应用。

  9. 应用在聊天窗口中使用自适应卡片进行响应。

  10. 选择“发送”。

    屏幕截图显示了自适应卡片,其中包含 Teams 中聊天消息中的搜索结果。

恭喜! 你做到了! 你已了解如何使用 OpenAPI 说明文档创建基于 API 的消息扩展。

另请参阅

基于 API 的消息扩展的身份验证