You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在基于AWS Lambda与Node.js的Alexa问答技能中从API动态添加槽位值

嘿,我完全懂你这种头疼的感觉——手动往自定义槽位里加答案,刚开始问题少还行,等内容多起来根本没法维护!刚好我之前做过类似的Alexa问答技能,给你几个实用的解决方案,都是亲测有效的:

方案1:会话内动态添加槽位值(临时生效)

这是Alexa官方推荐的动态更新槽位的方式,完美适配你的场景:每次从API拉取新问题后,把对应的答案临时添加到槽位里,只在当前会话生效,不用修改技能的永久配置。

用Node.js的ASK SDK v2实现的话,你可以在返回给Alexa的响应里添加Dialog.UpdateDynamicEntities指令,代码示例如下:

const { Alexa } = require('ask-sdk-core');

// 生成动态实体指令的工具函数
const createDynamicEntitiesDirective = (answers, slotName = "TriviaAnswer") => {
  return {
    type: "Dialog.UpdateDynamicEntities",
    updateBehavior: "REPLACE", // 替换当前会话的动态实体,也可用MERGE合并已有值
    types: [
      {
        name: slotName,
        values: answers.map(answer => ({
          id: answer.toLowerCase().replace(/\s+/g, "-"),
          name: {
            value: answer,
            synonyms: [] // 如果答案有同义词,比如"USA"和"美国",可以加在这里
          }
        }))
      }
    ]
  };
};

// 处理问答问题的Intent Handler
const TriviaQuestionHandler = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === "IntentRequest"
      && Alexa.getIntentName(handlerInput.requestEnvelope) === "TriviaQuestionIntent";
  },
  async handle(handlerInput) {
    // 1. 调用API获取当前问题和对应的答案数组
    const triviaData = await fetchTriviaFromYourAPI(); // 替换成你的API调用逻辑
    const { question, answers } = triviaData;

    // 2. 把答案存入会话属性,后面验证用户答案时会用到
    handlerInput.attributesManager.setSessionAttributes({
      correctAnswers: answers
    });

    // 3. 添加动态实体指令,让Alexa识别这些答案
    handlerInput.responseBuilder.addDirective(
      createDynamicEntitiesDirective(answers)
    );

    // 4. 返回问题给用户
    return handlerInput.responseBuilder
      .speak(`问题来了:${question} 请说出你的答案。`)
      .reprompt(`请说出你的答案哦。`)
      .getResponse();
  }
};

注意点:

  • 每个会话最多支持10个动态实体,每个实体最多1000个值,一般问答场景完全够用
  • 动态实体只在当前会话有效,会话结束后就会失效,适合每次会话内容不同的情况
方案2:批量更新自定义槽位(永久生效)

如果你的问答库是定期更新的(比如每天同步一次),可以写一个独立的Node.js脚本,调用Alexa的SMAPI(Skills Management API)来批量更新自定义槽位的永久值。

步骤大概是:

  1. 从Alexa开发者控制台获取你的客户端ID、客户端密钥和技能ID
  2. 调用LWA接口获取访问令牌
  3. 调用SMAPI更新槽位值,然后触发技能模型构建

代码示例:

const axios = require('axios');
const { URLSearchParams } = require('url');

// 获取LWA访问令牌(用于调用SMAPI)
async function getLWAToken(clientId, clientSecret) {
  const params = new URLSearchParams();
  params.append('grant_type', 'client_credentials');
  params.append('scope', 'alexa::ask:skills:readwrite');

  const response = await axios.post('https://api.amazon.com/auth/o2/token', params, {
    auth: { username: clientId, password: clientSecret }
  });
  return response.data.access_token;
}

// 更新自定义槽位值
async function updateSlotValues(skillId, slotName, allAnswers, token) {
  const apiUrl = `https://api.amazonalexa.com/v1/skills/${skillId}/slots/${slotName}/values`;
  
  const payload = {
    values: allAnswers.map(answer => ({
      name: { value: answer, synonyms: [] }
    }))
  };

  await axios.put(apiUrl, payload, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    }
  });
}

// 触发技能模型构建(更新槽位后需要构建才能生效)
async function buildSkillModel(skillId, token) {
  const apiUrl = `https://api.amazonalexa.com/v1/skills/${skillId}/build`;
  await axios.post(apiUrl, {}, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    }
  });
}

// 主脚本:拉取所有答案并更新槽位
async function main() {
  // 替换成你的实际信息
  const CLIENT_ID = "你的Alexa开发者客户端ID";
  const CLIENT_SECRET = "你的Alexa开发者客户端密钥";
  const SKILL_ID = "你的技能ID";
  const SLOT_NAME = "TriviaAnswer";

  // 1. 从API拉取所有问答的答案
  const allAnswers = await fetchAllTriviaAnswersFromAPI(); // 替换成你的API调用逻辑

  // 2. 获取令牌并更新槽位
  const token = await getLWAToken(CLIENT_ID, CLIENT_SECRET);
  await updateSlotValues(SKILL_ID, SLOT_NAME, allAnswers, token);
  
  // 3. 触发模型构建
  await buildSkillModel(SKILL_ID, token);
  
  console.log("槽位值更新完成,技能模型正在构建!");
}

// 运行脚本
main().catch(err => console.error("更新失败:", err));

注意点:

  • 需要确保你的客户端ID拥有alexa::ask:skills:readwrite权限
  • SMAPI有调用频率限制,不要频繁调用
  • 模型构建需要几分钟时间,完成后新的槽位值才会生效
方案3:跳过槽位,直接做模糊匹配(更灵活)

如果你的答案有很多变体(比如同义词、拼写差异),或者答案更新特别频繁,其实可以不用自定义槽位,直接用AMAZON.SearchQuery槽位接收用户输入,然后在Lambda里做模糊匹配。

比如用string-similarity库来对比用户输入和正确答案的相似度:

const { Alexa } = require('ask-sdk-core');
const stringSimilarity = require('string-similarity'); // 需要先npm install string-similarity

const TriviaAnswerHandler = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === "IntentRequest"
      && Alexa.getIntentName(handlerInput.requestEnvelope) === "TriviaAnswerIntent";
  },
  handle(handlerInput) {
    // 1. 获取用户输入的答案
    const userAnswer = Alexa.getSlotValue(handlerInput.requestEnvelope, "UserAnswer"); // 这里的UserAnswer是AMAZON.SearchQuery槽位
    
    // 2. 从会话属性获取之前存储的正确答案
    const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
    const correctAnswers = sessionAttributes.correctAnswers;

    // 3. 模糊匹配相似度
    const matchResult = stringSimilarity.findBestMatch(userAnswer, correctAnswers);
    const similarityThreshold = 0.7; // 可以根据需求调整,比如0.6或0.8

    if (matchResult.bestMatch.rating >= similarityThreshold) {
      return handlerInput.responseBuilder
        .speak("太棒了!答对啦!")
        .getResponse();
    } else {
      return handlerInput.responseBuilder
        .speak(`答错啦,正确答案是${correctAnswers.join("、")}。`)
        .getResponse();
    }
  }
};

注意点:

  • 这种方式不需要维护任何槽位值,灵活性极高
  • 相似度阈值需要根据你的答案类型调整,比如答案是短单词的话阈值可以设高一点,长句子的话可以设低一点

内容的提问来源于stack exchange,提问作者Prana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:48:10