AI Assistant是TinyMCE首次在编辑器中集成AI功能的迭代版本。TinyMCE AI是目前的AI解决方案,通过Chat、Quick Actions和Review模式提供深度文档感知的AI功能。TinyMCE AI可同时配合Tiny Cloud和自托管的TinyMCE编辑器使用,并能连接到托管的Tiny Cloud AI服务或自托管AI服务。

简介

本指南介绍如何在TinyMCE中使用Amazon Bedrock集成AI Assistant插件。Amazon Bedrock是一项用于在AWS上构建生成式AI应用程序的托管服务。使用Amazon Bedrock的优势在于它提供了多种基础模型,这些模型几乎无需修改即可互换使用。

以下示例直接在客户端加载AWS凭证。出于安全考虑,建议使用其他方法获取这些凭证。建议将API调用隐藏在服务器端代理之后,以避免将AWS凭证暴露给客户端。

这些示例使用Node.js并通过@aws-sdk/client-bedrock-runtime包调用AWS SDK for JavaScript来与Amazon Bedrock API交互。不过,您可以使用AWS SDK支持的任何开发环境。

这里以Anthropic Claude-3 Haiku模型为例。您可以将modelId替换为您想使用的模型。更多信息请参阅支持的模型。请注意,每个基础模型都有其自己的参数集。

若要了解字符串响应与流式响应的区别,请参阅插件页面上的respondWith对象。

先决条件

开始之前,您需要准备以下内容:

  1. 一个具有Amazon Bedrock访问权限的AWS账户。

  2. 该账户的AWS凭证。

  3. 一个已安装@aws-sdk/client-bedrock-runtime包的Node.js环境。

以下示例旨在展示如何在客户端集成中使用身份验证凭证调用API。不建议在生产环境中采用此方式。建议仅通过代理服务器或实现服务器端集成来访问API,以防止未经授权的API访问。

字符串响应

本示例演示如何将AI Assistant插件与InvokeModel命令集成以生成字符串响应。

import { BedrockRuntimeClient, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime";

// 不建议在生产环境使用中直接提供访问凭证
// 建议配置代理服务器进行请求认证并授权访问
const AWS_ACCESS_KEY_ID = "<YOUR_ACCESS_KEY_ID>";
const AWS_SECRET_ACCESS_KEY = "<YOUR_SECRET_ACCESS_KEY>";
const AWS_SESSION_TOKEN = "<YOUR_SESSION_TOKEN>";

const config = {
  region: "us-east-1",
  credentials: {
    accessKeyId: AWS_ACCESS_KEY_ID,
    secretAccessKey: AWS_SECRET_ACCESS_KEY,
    sessionToken: AWS_SESSION_TOKEN,
  },
};
const client = new BedrockRuntimeClient(config);

const ai_request = (request, respondWith) => {
  const payload = {
    anthropic_version: "bedrock-2023-05-31",
    max_tokens: 1000,
    messages: [{
      role: "user",
      content: request.prompt
    }],
  };

  const input = {
    body: JSON.stringify(payload),
    contentType: "application/json",
    accept: "application/json",
    modelId: "anthropic.claude-3-haiku-20240307-v1:0"
  };

  respondWith.string(async (_signal) => {
    const command = new InvokeModelCommand(input);
    const response = await client.send(command);
    const decodedResponseBody = new TextDecoder().decode(response.body);
    const responseBody = JSON.parse(decodedResponseBody);
    const output = responseBody.content[0].text;
    return await output;
  });
};

tinymce.init({
  selector: 'textarea',
  plugins: 'ai code help',
  toolbar: 'aidialog aishortcuts code help',
  ai_request
});

流式响应

本示例演示如何将AI Assistant插件与InvokeModelWithResponseStream命令集成以生成流式响应。

import { BedrockRuntimeClient, InvokeModelWithResponseStreamCommand } from "@aws-sdk/client-bedrock-runtime";

// 不建议在生产环境中于集成内部提供访问凭证。
// 建议设置代理服务器以验证请求并提供访问权限。
const AWS_ACCESS_KEY_ID = "<YOUR_ACCESS_KEY_ID>";
const AWS_SECRET_ACCESS_KEY = "<YOUR_SECRET_ACCESS_KEY>";
const AWS_SESSION_TOKEN = "<YOUR_SESSION_TOKEN>";

const config = {
  region: "us-east-1",
  credentials: {
    accessKeyId: AWS_ACCESS_KEY_ID,
    secretAccessKey: AWS_SECRET_ACCESS_KEY,
    sessionToken: AWS_SESSION_TOKEN,
  },
};
const client = new BedrockRuntimeClient(config);

const ai_request = (request, respondWith) => {
  // 将每个之前的查询和响应作为独立消息添加
  const conversation = request.thread.flatMap((event) => {
    if (event.response) {
      return [
        { role: 'user', content: event.request.query },
        { role: 'assistant', content: event.response.data }
      ];
    } else {
      return [];
    }
  });

  // 插件提供的系统消息,用于将输出格式化为 HTML 内容。
  const pluginSystemMessages = request.system.map((text) => ({
    text
  }));

  const systemMessages = [
    ...pluginSystemMessages,
    // 用于控制 AI 输出的额外系统消息
    { text: '不要在开头包含 html\`\`\` 或在结尾包含 \`\`\`。' },
    { text: '无需解释或模板内容,仅返回 HTML 响应。' }
  ]

  const system = systemMessages.map((message) => message.text).join('\n');

  // 构建发送至 API 的新查询
  const text = request.context.length === 0 || conversation.length > 0
    ? request.query
    : `Question: ${request.query} Context: """${request.context}"""`;

  const messages = [
    ...conversation,
    {
      role: "user",
      content: text
    }
  ];

  const payload = {
    anthropic_version: "bedrock-2023-05-31",
    max_tokens: 1000,
    system,
    messages,
  };

  const input = {
    body: JSON.stringify(payload),
    contentType: "application/json",
    accept: "application/json",
    modelId: "anthropic.claude-3-haiku-20240307-v1:0"
  };

  // Amazon Bedrock 不支持在流传输过程中取消响应,因此信号回调无用途。
  respondWith.stream(async (_signal, streamMessage) => {
    const command = new InvokeModelWithResponseStreamCommand(input);
    const response = await client.send(command);
    for await (const item of response.body) {
      const chunk = JSON.parse(new TextDecoder().decode(item.chunk.bytes));
      const chunk_type = chunk.type;

      switch (chunk_type) {
        case "message_start":
          break;
        case "content_block_start":
          break;
        case "content_block_delta":
          const message = chunk.delta.text;
          streamMessage(message);
          break;
        case "content_block_stop":
          break;
        case "message_delta":
          break;
        case "message_stop":
          break;
        default:
          return Promise.reject("Stream error");
      }
    }
  });
};

tinymce.init({
  selector: 'textarea',
  plugins: 'ai code help',
  toolbar: 'aidialog aishortcuts code help',
  ai_request
});