自定义生成的处理程序 customize-generated-handler

IMPORTANT
Adobe LLM Apps当前在Beta中。
此处显示的功能、工作流和UI不一定表示产品的最终状态。 要加入Beta,请发送电子邮件至llm-apps-beta@adobe.com 。

平台会为每个生成的操作创建一个工作处理程序。 处理程序最初会返回用于测试完整体验的示例数据。

在本指南中:​了解如何查找生成的处理程序、如何将其示例数据替换为对您的系统的调用,以及如何使其响应与构件兼容。 然后测试和部署更改。

查找生成的处理程序

打开基础结构设置期间连接的处理程序存储库。 如果您使用Adobe托管基础结构,请先请求代码访问权限,然后再继续。

actions/
└── <action-slug>/
    ├── index.js
    └── data.json

匹配的测试位于单独的目录中:

test/
└── actions/
    └── <action-slug>.test.js

使用生成的拉取请求来查找确切的文件。 文件夹概要文件使用连字符:平台在actions/search-products/下生成了一个代码标识符为search_products的操作。 运行时可识别断字表单以及与该标识符完全匹配的文件夹。

编辑生成的index.js和任何关联的示例数据。 请勿更改运行时文件,如entry.js。

处理程序合同

每个处理程序导出一个异步函数:

module.exports = async (args, extra) => {
  return {
    content: [
      { type: 'text', text: 'Response for the LLM platform.' }
    ],
    structuredContent: {
      // Data for the widget.
    }
  };
};

对于具有输入架构的操作,该函数接收args对象和可选运行时上下文extra,并返回结果对象。 将extra用于运行时帮助程序,如getVariable(extra, 'PRODUCT_API_ORIGIN');应用程序变量不会通过process.env自动公开。 请参阅配置应用程序变量和密钥。

输入: args

args包含为LLM Apps中的操作定义的参数。

对于具有category和query参数的操作:

module.exports = async ({ category = '', query = '' } = {}, extra) => {
  // Validate business rules before using the arguments.
};

通过操作元数据,运行时将声明的输入架构转换为对受支持规则的验证,包括必填字段、原始类型、枚举和数组项。 运行时未执行完整的JSON架构验证。 检查约束,例如字符串长度、数字范围和处理程序中的嵌套对象属性。

没有架构元数据的本地发现与部署不相等:运行时未以正常(args, extra)形式传递操作参数。 使用直接处理程序测试或代表性本地actions.json元数据测试参数。 请参阅本地处理程序开发和测试。

输出: content

始终返回content。 它是LLM平台和不显示小部件的主机读取的内容部分的数组。

content: [
  {
    type: 'text',
    text: 'Found 3 products matching your search.'
  }
]

保持文本响应简洁。 不包括凭据、内部错误或用户无权查看的数据。

输出: structuredContent

当操作具有小部件时,返回structuredContent。 它必须是纯对象,而不是空数组。

structuredContent: {
  arguments: { query: 'coffee' },
  records: [
    {
      id: 'P-100',
      name: 'Frescopa House Blend',
      price: '$14.99'
    }
  ]
}

structuredContent是交付给主机的MCP操作结果的一部分。 请勿假设模型或用户隐藏了该变量。 仅返回体验、从不凭据或专用服务器配置所需的授权数据。

对于纯文本操作,返回structuredContent是可选的。

处理程序 — 小部件合同

处理程序和构件共享一个协定:structuredContent的形状。

处理程序输出
用途
content
模型可读响应和纯文本回退
structuredContent
数据已传送到主机并由小组件通过bridge.toolResult读取

该构件从LLM应用程序SDK Bridge中读取处理程序结果:

export default async function decorate(block, bridge) {
  if (!bridge?.isConnected || bridge.hostContext?.preview === true) {
    // Render the block's local preview data instead.
    return;
  }
  const result = await bridge.toolResult;
  const records = result?.structuredContent?.records ?? [];

  // Render records.
}

当前生成的处理程序使用包含arguments和records的对象。 现有应用程序可以使用不同的形状,因此在编辑之前请检查您生成的文件。 更改字段名称或类型可能会破坏构件。 同时更新处理程序、小部件和测试。

替换示例数据

生成的处理程序从data.json加载代表性记录。 将示例数据查找替换为对您的系统的服务器端调用。 以下示例使用公共HTTPS目录API并从应用程序变量中读取其非敏感来源。 将响应验证调整为适合您的API。

const { getVariable } = require('@adobe/llm-apps-runtime');

module.exports = async ({ query = '' } = {}, extra) => {
  const normalizedQuery = typeof query === 'string' ? query.trim() : '';
  if (!normalizedQuery || normalizedQuery.length > 200) {
    return {
      content: [{ type: 'text', text: 'Enter a valid product search.' }],
      structuredContent: { arguments: {}, records: [] },
      isError: true
    };
  }

  const knownArguments = { query };
  try {
    const apiOrigin = getVariable(extra, 'PRODUCT_API_ORIGIN');
    if (!apiOrigin) {
      throw new Error('Missing configuration');
    }
    const origin = new URL(apiOrigin);
    if (origin.protocol !== 'https:' || origin.username || origin.password) {
      throw new Error('Invalid API origin');
    }
    const url = new URL('/v1/products', origin);
    url.searchParams.set('query', normalizedQuery);

    const response = await fetch(url, {
      signal: AbortSignal.timeout(8000)
    });
    if (!response.ok) {
      throw new Error('Request failed');
    }
    const payload = await response.json();
    if (!payload || !Array.isArray(payload.products)
        || !payload.products.every((product) =>
          product
          && typeof product.id === 'string'
          && typeof product.name === 'string'
          && typeof product.price === 'string')) {
      throw new Error('Unexpected response');
    }

    const records = payload.products.map(({ id, name, price }) => ({
      id,
      name,
      price
    }));

    return {
      content: [
        { type: 'text', text: `Found ${records.length} matching products.` }
      ],
      structuredContent: { arguments: knownArguments, records }
    };
  } catch {
    return {
      content: [{ type: 'text', text: 'Product search is unavailable. Try again later.' }],
      structuredContent: { arguments: knownArguments, records: [] },
      isError: true
    };
  }
};

在处理程序中保留受保护的网络访问。 切勿将API凭据放入小组件JavaScript或源代码控制中。 有关代表登录用户发出的请求,请参阅对最终用户进行身份验证。

有关您的处理程序所需的非敏感设置,如服务URL,请参阅配置应用程序变量和密钥。 为每个环境单独配置变量。 变量更改将在下次部署时生效。 当前UI仅支持添加变量;请勿使用变量存储API凭据。

处理预期状态

保留每个结果的可预测输出形状。

找到个结果

{
  content: [{ type: 'text', text: 'Found 3 products.' }],
  structuredContent: { arguments: { query: 'coffee' }, records: [...] }
}

无结果

{
  content: [{ type: 'text', text: 'No matching products were found.' }],
  structuredContent: { arguments: { query: 'coffee' }, records: [] }
}

可预测的结果形状允许构件呈现空状态,而无需猜测records是否存在。

对于服务故障,返回带有isError: true的安全错误结果,如示例所示。 检查小部件中的result.isError,以便将失败的请求显示为成功的空搜索。 不要公开栈栈跟踪、凭据、内部主机或上游响应主体。

测试合同

每当处理程序更改时,请更新生成的测试。 封面:

  • 参数有效且无效。
  • “结果”和“无结果”状态。
  • API失败和超时。
  • API响应的格式不正确。
  • 每个结果中都存在content。
  • structuredContent的纯对象。
  • 构件所需的形状。

运行存储库配置的测试套件:

npm test

如果生成的测试导入node:test而不是Jest,请使用node --test test/actions/<action-slug>.test.js运行它,并验证其导入路径是否从test/actions/解析。 使用适合该文件的测试运行器;样板的npm test命令运行Jest。

有关本地MCP测试,请参阅本地处理程序开发和测试。

部署更改

  1. 提交并推送处理程序更改。
  2. 如果更改数据形状,请更新并推送构件。
  3. 将应用程序部署到暂存环境。
  4. 在支持的LLM平台中测试。 查看ChatGPT中的测试或Claude中的测试。
  5. 测试在暂存环境中通过后,部署到生产环境。

接下来,请参阅自定义生成的构件。

recommendation-more-help
llm-apps-help-main-toc