亚马逊AWS官方博客

基于亚马逊云科技 HAQM Bedrock Tool Use 实现 Generative UI

背景

在当前 AI 应用开发浪潮中,越来越多的开发者专注于构建基于大语言模型(LLM)的 chatbot 和 AI Agent。然而,传统的纯文本对话形式存在局限性,无法为用户提供足够直观和丰富的交互体验。为了增强用户体验,开发者们开始探索在对话界面中动态插入 UI 组件的解决方案。

虽然 LLM 具备生成 HTML/CSS 代码的能力,但这种方式往往缺乏标准化和可靠性,生成的代码质量参差不齐,难以维护和复用。特别是在企业级应用中,我们需要确保生成的 UI 组件符合既定的设计规范和技术标准。

因此,一个更优的方案是利用 LLM 的 Tool Use(工具调用)能力,通过预定义的函数接口来生成标准化的 Web 前端组件。这种方法不仅能确保组件的质量和一致性,还能够更好地与现有的前端框架和组件库进行集成。本文将探讨如何基于 HAQM Bedrock 的 Tool Use 功能来实现这一目标,构建可靠、可复用的生成式 UI 解决方案。

传统方法的局限性

在传统的 LLM 生成 UI 实践中,直接让模型生成 HTML/CSS 代码存在诸多挑战:

输出的不确定性

  • 生成代码的质量不稳定,可能会出现语法错误导致的 HTML 无法正确渲染;
  • 同样的自然语言输入描述可能产生完全不同的输出样式;
  • 浏览器直接渲染 LLM 输出的代码存在安全性问题。

工程化问题

  • 难以与现代前端工程体系深度整合;
  • 组件复用性差,无法建立统一的设计规范;
  • 无法高效利用成熟的组件库(如 Ant Design、MUI 以及 Shadcn 等)。

生态系统割裂

  • 无法高效利用成熟的组件库(如 Ant Design、Material-UI);
  • 样式系统难以统一,影响产品设计一致性;
  • 缺乏与现代构建工具和开发流程的无缝衔接。

Tool Use 方案概述

基于 Tool Use 的 UI 生成方案提供了一个更加结构化和可控的解决方案:

核心架构

  • 预定义标准化组件接口,形成可调用的组件集合;
  • 通过 JSON Schema 严格约束组件结构和属性,并允许组件嵌套组件的方式;
  • 采用递归组合模式构建复杂的组件树。

工作流程

用户输入 → LLM 解析需求 → 组件选择与组合 → 生成 JSON 组件树 → 前端渲染

技术优势

  • 可靠性:从 LLM 直接生成代码转换为 LLM 选择组件再组合,确保生成可预期的结果。
  • 可维护性:标准化的组件定义便于统一管理和更新。
  • 扩展性:易于整合新的组件。
  • 安全性:LLM 只输出 JSON 组件树,浏览器端不需要直接渲染 HTML,提高安全性。

生态整合

  • 无缝对接现有组件库;
  • 支持主题定制和样式覆盖,比如通过 Tailwind 等方式,主题样式可复用;
  • 便于引入状态管理和数据流方案,比如可以动态生成 Checkout 组件等完成交易类操作行为。

实现方案详解

本方案的实现主要包含两个核心部分:组件工具定义和前端渲染实现。让我们深入了解每个部分的具体实现细节。

组件工具定义

我们将生成 UI 组件抽象成 LLM 可用的 1 个工具,基于 Tool Use JSON Schema 来定义组件生成工具,这种方式具有以下优势:

  • 支持组件的递归嵌套
  • 确保生成的组件结构符合预期
    const generateUI = {
      name: 'generateUI',
      description: 'Generate UI components dynamically to display data only when user ask you to render a UI component based on the data user provided. If other tool already used, you dont have to use this tool to generate component.',
      input_schema: {
        type: 'object',
        properties: {
          component: {
            anyOf: [
              { $ref: '#/$defs/CardList' },
              { $ref: '#/$defs/Email' },
              { $ref: '#/$defs/ProductCard' },
              { $ref: '#/$defs/Table' }
            ]
          }
        },
        required: ['component'],
        $defs: {
          component: {
            anyOf: [
              { $ref: '#/$defs/CardList' },
              { $ref: '#/$defs/Email' },
              { $ref: '#/$defs/ProductCard' },
              { $ref: '#/$defs/Table' }
            ]
          },
          CardList: {
            type: 'object',
            description: 'Vertical Card List component.',
            properties: {
              name: { type: 'string', enum: ['CardList'] },
              children: { type: 'array', items: { $ref: '#/$defs/component'}}
            },
            required: ['name', 'children']
          },
          Email: {
            type: 'object',
            properties: {
              name: { type: 'string', enum: ['Email'] },
              to: { type: 'string', description: 'Email sent to address' },
              from: { type: 'string', description: 'Email sent from address' },
              subject: { type: 'string', description: 'Email subject' },
              html: { type: 'string', description: 'Email content in html format' },
            },
            required: ['name', 'to', 'from', 'subject', 'html']
          },
          ProductCard: {
            type: 'object',
            properties: {
              name: { type: 'string', enum: ['ProductCard'] },
              image: { type: 'string', description: 'Product image url' },
              title: { type: 'string', description: 'Product title' },
              price: { type: 'string', description: 'Product price, including currency symbol (e.g., $
    20 €15 ¥100' },
              description: { type: 'string', description: 'Product description' },
            },
            required: ['name', 'image', 'title', 'price', 'description']
          },
          Table: {
            type: 'object',
            properties: {
              name: { type: 'string', enum: ['Table'] },
              headers: {
                type: 'array',
                items: { type: 'string', description: 'Table headers'}
              },
              rows: {
                type: 'array',
                items: { type: 'array', items: { type: 'string', description: 'Each column value based on headers'}},
                description: 'Each row'
              }
            },
            required: ['name', 'headers', 'rows']
          }
        }
      }
    }
    

组件定义中包含了几种常用的 UI 组件类型:

  • CardList:用于垂直展示卡片列表
  • Email:邮件展示组件
  • ProductCard:商品卡片组件
  • Table:表格组件

每个组件都定义了必要的属性和类型约束,确保生成的 JSON 数据结构的完整性。

前端渲染实现

前端渲染层采用了组件映射和递归渲染的方案,实现了灵活的组件树构建:

const componentMap = {
  Email: Email,
  CardList: CardList,
  ProductCard: ProductCard,
  HorizontalScrollArea: HorizontalScrollArea,
  Table: TableComponent
}

const renderComponent = (component) => {
  const { name, children = [], ...props } = component
  if (name) {
    return React.createElement(
      componentMap[name],
      props,
      ...children.map(item => renderComponent(item))
    )
  }
}

渲染实现的关键特性:

  • 组件映射:通过 componentMap 建立组件名称到实际组件的映射关系。
  • 属性传递:确保组件属性被正确传递到对应的 React 组件。
  • 递归渲染:支持任意深度的组件嵌套结构。
  • 动态创建:使用 createElement 动态构建组件树。

LLM 输出示例

比如输入包含了商品信息,并且 Agent 应用决策需要渲染组件,那么输出如下,这个组件树可在前端动态渲染。

const uiTree = {
  name: 'CardList',
  children: [{
    name: 'ProductCard',
    image: 'product1.jpg',
    title: 'Product 1',
    price: '$99.99',
    description: 'Amazing product'
  }, {
    name: 'ProductCard',
    image: 'product2.jpg',
    title: 'Product 2',
    price: '$149.99',
    description: 'Another great product'
  }]
}

示例应用场景

动态生成 Table

电商商品推荐,生成商品列表

生成 DSL

通过 Tool Use 的 JSON Schema 实现组件生成,实际上揭示了一种可能更普遍的模式:利用结构化模式定义来约束和引导 LLM 的输出。这种方法本质上是在设计一套领域特定语言(DSL),它不仅仅局限于工具调用,更是在 LLM 和下游系统之间构建了一层可靠的抽象桥梁。

这种抽象模式具有以下优势:

1. 输出可控性

  • 通过 Schema 约束确保输出格式符合预期;
  • 降低解析和验证的复杂度;
  • 提供类型安全和结构化保证。

2. 系统解耦

  • LLM 不需要了解下游系统的具体实现细节;
  • 下游系统可以独立演进而不影响 LLM 调用层;
  • 便于在 LLM 和系统之间添加中间件层(如验证、转换、日志等)。

3. 应用场景扩展除了 UI 组件生成,这种模式可以扩展到多个领域:

  • 数据查询:将自然语言转换为结构化查询语言(比如 GraphQL 或自定义查询 DSL),尤其是 text2sql 场景,LLM 生成的 SQL 直接运行查询并不可靠,而且还需要做额外的提示词工程来规避不同计算引擎 SQL 语法兼容性的问题;
  • 工作流编排:通过 JSON 描述任务流程和依赖关系;
  • 配置生成:为复杂系统生成规范化的配置文件。

4. 安全性增强

  • 避免直接执行 LLM 生成的代码;
  • 提供验证和净化的中间层;
  • 实现细粒度的权限控制和行为约束。

实践建议:

1. 抽象层设计

  • 保持简单,避免过度抽象,当 1 个 Tool 的 schema 定义过于复杂的时候,适当考虑拆分 Tool;
  • 预留扩展空间。

2. 验证机制

  • 实现严格的 Schema 验证;
  • 添加运行时类型检查。

这种基于 Schema 的抽象模式正在成为 AI 原生应用开发中的一种最佳实践。它不仅提供了一种规范化的方式来处理 LLM 输出,更为构建可靠、可维护的 AI 系统提供了重要的架构基础。随着 AI 应用的不断发展,这种模式将在更广泛的场景中发挥作用,帮助开发者构建更加健壮和可扩展的 AI Agent 系统。


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您了解行业前沿技术和发展海外业务选择推介该服务。

本篇作者

罗震

亚马逊云科技解决方案架构师,负责基于亚马逊云科技的云计算方案架构咨询和设计。