别再只让智能体回文字了!给它装上交互式 UI 吧!

0 阅读18分钟

前面的文章我们介绍了 Google 开源的 A2UI 协议,它让 AI 智能体告别纯文本,拥抱交互式界面。今天,我们将通过一个完整的实战项目,手把手教你如何集成 LangChain、MCP 服务和 A2UI,构建一个交互式的智能天气查询应用。

📖 引言

在上一篇文章中,我们了解了 A2UI 的核心价值:让 AI 智能体能够生成丰富的交互式界面,而不仅仅是文本回复。今天,我们要做的不仅仅是一个演示,而是一个完整的、可落地的生产级应用

这个项目将展示:

  • ✅ 如何使用LangChain构建智能体
  • ✅ 如何通过**MCP(Model Context Protocol)**调用高德天气 API
  • ✅ 如何集成Google A2UI生成交互式界面
  • ✅ 如何使用Vue 3前端框架渲染 A2UI 组件

让我们开始吧!

🎯 项目概览

最终效果

用户输入:"今天杭州天气怎么样?"

智能体不仅回复文本,还会生成一个完整的交互式界面,包含:

  • 📝 城市输入表单
  • 🔘 查询天气按钮
  • 📊 天气结果卡片(温度、湿度、风速等)
  • 🔄 重新查询功能

技术架构

┌──────────────────────────────────────────────────────────────────────┐
│                            用户界面层 (UI Layer)                     │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │ Vue 3 前端应用                                                 │  │
│  │ • 用户输入界面                                                 │  │
│  │ • A2UI 渲染器                                                  │  │
│  │ • A2UI 组件:Text, Button, TextField, Card 等                  │  │
│  └────────────────────────────────────────────────────────────────┘  │
└───────────────────────────────┬──────────────────────────────────────┘
                                │ HTTP POST /api/chat
                                │ HTTP POST /api/action
                                ▼
┌──────────────────────────────────────────────────────────────────────┐
│                           后端服务层 (Backend Layer)                 │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │ FastAPI 服务 (app.py)                                          │  │
│  │ • /api/chat   → 处理用户消息                                   │  │
│  │ • /api/action → 处理 A2UI 动作事件                             │  │
│  └───────────────────────┬────────────────────────────────────────┘  │
│                          │                                           │
│                          ▼                                           │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │ LangChain 智能体                                               │  │
│  │ • 解析用户意图                                                 │  │
│  │ • 调用工具(如 query_weather)                                 │  │
│  │ • 生成结构化输出(WeatherAgentOutput)                         │  │
│  └───────────────────────┬────────────────────────────────────────┘  │
│                          │                                           │
│                          ▼                                           │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │ 通义千问 (Qwen) LLM                                            │  │
│  │ • 通过 DashScope API 调用                                      │  │
│  │ • 输出结构化 JSON 响应                                         │  │
│  └───────────────────────┬────────────────────────────────────────┘  │
│                          │                                           │
│                          ▼                                           │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │ A2UI JSON 生成器                                               │  │
│  │ • generate_weather_form_ui()                                   │  │
│  │ • generate_weather_result_ui()                                 │  │
│  │ • 将智能体输出 → 转换为 A2UI 消息                              │  │
│  └────────────────────────────────────────────────────────────────┘  │
└───────────────────────────────┬──────────────────────────────────────┘
                                │
                                ▼
┌──────────────────────────────────────────────────────────────────────┐
│                           工具服务层 (Tool Layer)                    │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │ MCP 工具适配层                                                 │  │
│  │ • get_weather_data_via_mcp()                                   │  │
│  │ • 支持连接方式:HTTP / SSE / stdio                             │  │
│  └───────────────────────┬────────────────────────────────────────┘  │
│                          │                                           │
│                          ▼                                           │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │ 高德地图天气 API                                               │  │
│  │ • URL: https://restapi.amap.com/v3/weather/weatherInfo         │  │
│  │ • 返回实时天气数据                                             │  │
│  └────────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────────┘

数据流向:
1. 用户输入 → Vue 前端发送 HTTP 请求
2. FastAPI 接收请求 → 调用 LangChain 智能体
3. LangChain 智能体 → 调用通义千问 LLM
4. LLM 返回结构化结果 → LangChain 工具调用
5. 工具调用 → MCP 服务 → 高德天气 API
6. 天气数据返回 → LangChain 生成输出
7. FastAPI 转换 → A2UI JSON 格式
8. Vue 前端接收 → 渲染 A2UI 组件

技术栈

后端:

  • FastAPI- 现代化的 Python Web 框架
  • LangChain- 构建 LLM 应用的框架
  • 通义千问(Qwen)- 阿里云的大语言模型
  • MCP- Model Context Protocol,用于调用外部工具
  • 高德地图 API- 提供天气数据

前端:

  • Vue 3- 渐进式 JavaScript 框架
  • Composition API- Vue 3 的组合式 API
  • Vite- 下一代前端构建工具
  • A2UI 渲染器- 自定义的 A2UI 组件渲染器

架构组件详情

层级组件技术栈职责
前端层Vue 3 应用Vue 3, TypeScript, Vite用户交互,发送请求,渲染UI
A2UI 渲染器Vue Components将JSON转为可视化组件
应用层FastAPI 服务器Python, FastAPI, PydanticREST API,请求路由,响应处理
LangChain 智能体Python, LangChain自然语言处理,意图识别,工具调度
Qwen LLMDashScope API大语言模型,结构化输出
A2UI 生成器Python, Jinja2生成标准化的UI JSON响应
工具层MCP 工具适配Python, MCP Protocol工具注册,参数验证,错误处理
高德天气APIHTTP, JSON获取实时天气和预报数据
数据处理Python, Redis数据转换,格式化,缓存

🛠️ 环境准备

1. 基础环境

Python 环境:

  • Python 3.8 或更高版本(推荐 3.11+)
  • pip 包管理器

Node.js 环境:

  • Node.js 18 或更高版本(推荐 20+)
  • npm 包管理器

验证安装:

python --version  # 应显示 Python 3.8+
node --version    # 应显示 v18.0.0+
npm --version     # 应显示 8.0.0+

2. 获取 API Keys

2.1 通义千问 API Key

  1. 访问 阿里云 DashScope
  2. 注册/登录账号
  3. 创建 API Key
  4. 复制 API Key 备用

2.2 高德地图 API Key

  1. 访问 高德开放平台
  2. 注册/登录账号
  3. 创建应用并选择 "Web 服务" 类型
  4. 获取 API Key 备用

💡提示:如果没有 API Key,项目也会运行,但会使用模拟数据。

📦 项目结构

weather-a2ui/
├── backend/                      # 后端服务
│   ├── app.py                   # LangChain 智能体主程序
│   ├── main.py                  # 简化版后端(可选)
│   ├── requirements.txt         # Python 依赖
│   ├── .env                    # 环境变量
│   └── MCP_SETUP.md            # MCP 配置说明
│
├── frontend/                     # 前端应用
│   ├── package.json            # Node.js 依赖
│   ├── vite.config.js          # Vite 配置
│   ├── index.html              # HTML 入口
│   └── src/
│       ├── main.js             # Vue 应用入口
│       ├── App.vue             # 主应用组件
│       ├── A2UIRenderer.vue    # A2UI 渲染器
│       └── components/         # A2UI 组件实现
│           ├── A2UIComponent.vue
│           ├── A2UIText.vue
│           ├── A2UIButton.vue
│           ├── A2UITextField.vue
│           ├── A2UIColumn.vue
│           ├── A2UIRow.vue
│           └── A2UICard.vue
│
└── README.md                    # 项目说明

🔧 后端实现详解

1. 安装后端依赖

cd backend
pip install -r requirements.txt

核心依赖包括:

  • fastapi==0.115.9- Web 框架
  • langchain>=0.2.0- LangChain 核心库
  • langchain-community>=0.2.0- LangChain 社区工具
  • dashscope>=1.14.0- 通义千问 SDK
  • httpx>=0.25.0- 异步 HTTP 客户端
  • mcp>=1.0.0- MCP 客户端库
  • python-dotenv>=1.0.0- 环境变量管理

2. 配置环境变量

backend目录下创建.env文件:

# 通义千问 API Key
DASHSCOPE_API_KEY=your-dashscope-api-key-here

# 可选:通义千问模型配置
QWEN_MODEL=qwen-turbo
QWEN_TEMPERATURE=0.7

# 高德天气 API 配置
MCP_GAODE_WEATHER_URL=https://restapi.amap.com
GAODE_API_KEY=your-gaode-api-key-here

# 可选:服务器配置
HOST=0.0.0.0
PORT=8000

📝注意:将your-xxx-api-key-here替换为你的实际 API Key。

3. 核心代码解析

3.1 LangChain 智能体定义

from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_community.chat_models import ChatTongyi
from langchain_core.tools import Tool

class WeatherAgentOutput(BaseModel):
    """智能体输出格式"""
    text: str = Field(description="给用户的文本回复")
    action: str = Field(description="动作类型: 'show_form' 或 'show_result'")
    city: Optional[str] = Field(default=None, description="城市名称")
    temperature: Optional[int] = Field(default=None, description="温度")
    condition: Optional[str] = Field(default=None, description="天气状况")
    humidity: Optional[int] = Field(default=None, description="湿度")
    wind_speed: Optional[int] = Field(default=None, description="风速")

这个 Pydantic 模型定义了智能体的输出结构,LangChain 会确保 LLM 的输出符合这个格式。

3.2 MCP 工具集成

async def get_weather_data_via_mcp(city: str) -> Dict[str, Any]:
    """通过 MCP 调用高德天气 API"""
    # 检测是否是直接调用高德 API
    if "restapi.amap.com" in url:
        # 直接调用高德 API
        return await get_weather_direct_gaode(city, api_key)
    
    # 或者通过 MCP 协议调用
    # ...

代码会自动检测配置的 URL,如果是高德 API,会直接使用标准的 REST API 调用方式。

3.3 A2UI JSON 生成

def generate_weather_form_ui(default_city: str = "北京"):
    """生成天气查询表单的 A2UI JSON"""
    return [
        {
            "beginRendering": {
                "surfaceId": "weather-form",
                "root": "root-column",
                "styles": {
                    "primaryColor": "#00BFFF",
                    "font": "Roboto"
                }
            }
        },
        {
            "surfaceUpdate": {
                "surfaceId": "weather-form",
                "components": [
                    # 定义组件树...
                ]
            }
        },
        {
            "dataModelUpdate": {
                "surfaceId": "weather-form",
                "contents": [
                    {
                        "key": "city",
                        "valueString": default_city
                    }
                ]
            }
        }
    ]

这个函数生成标准的 A2UI JSON 格式,包含:

  • beginRendering- 开始渲染指令
  • surfaceUpdate- 组件定义
  • dataModelUpdate- 数据模型更新

3.4 API 端点

@app.post("/api/chat", response_model=AgentResponse)
asyncdef chat(user_message: UserMessage):
    """处理用户消息,返回 A2UI 响应"""
    # 调用 LangChain 智能体
    result = await asyncio.to_thread(
        weather_agent, 
        {"input": user_message.message, "chat_history": []}
    )
    
    # 根据智能体输出生成 A2UI JSON
    if result.action == "show_form":
        a2ui_messages = get_form_ui(result.city or"北京")
    elif result.action == "show_result":
        a2ui_messages = get_result_ui(weather_data)
    
    return AgentResponse(
        text=result.text,
        a2ui_messages=a2ui_messages
    )

4. 启动后端服务

cd backend
python app.py

成功启动后,你会看到:

==================================================
天气查询助手 (LangChain + Qwen + MCP)
==================================================
配置文件: .env (如果存在)
模型: qwen-turbo
MCP 连接方式: http
✅ 已配置通义千问 API Key
✅ MCP 服务器: https://restapi.amap.com
   工具名称: query_weather
服务器: http://0.0.0.0:8000
==================================================

INFO:     Started server process
INFO:     Uvicorn running on http://0.0.0.0:8000

🎨 前端实现详解

1. 安装前端依赖

cd frontend
npm install

核心依赖:

  • vue@^3.3.4- Vue 3 框架
  • vite@^5.0.0- 构建工具
  • @vitejs/plugin-vue@^4.4.0- Vite Vue 插件

2. A2UI 渲染器实现

2.1 A2UIRenderer.vue - 核心渲染器

<template>
  <div class="a2ui-surface" v-if="surface && rootComponent">
    <A2UIComponent
      :component-id="surface.root"
      @action="$emit('action', $event)"
      @update-data="$emit('update-data', surface.id, $event.key, $event.value)"
    />
  </div>
</template>

<script setup>
import { computed, inject } from 'vue'
import A2UIComponent from './components/A2UIComponent.vue'

const props = defineProps({
  surface: Object,
  dataModel: Object
})

// 注入组件树和数据模型
const components = inject('components')
const dataModel = inject('dataModel')

const rootComponent = computed(() => {
  return components.value?.[props.surface?.root]
})
</script>

这个组件是 A2UI 渲染的入口,负责:

  • 接收 A2UI JSON 消息
  • 解析 surface(界面区域)
  • 渲染根组件树

2.2 A2UIComponent.vue - 组件路由

<template>
  <A2UIText v-if="componentType === 'Text'" :component="component" />
  <A2UIButton v-else-if="componentType === 'Button'" :component="component" />
  <A2UITextField v-else-if="componentType === 'TextField'" :component="component" />
  <A2UIColumn v-else-if="componentType === 'Column'" :component="component">
    <!-- 递归渲染子组件 -->
  </A2UIColumn>
  <!-- ... 其他组件 -->
</template>

这是组件分发器,根据组件类型路由到对应的具体组件实现。

2.3 具体组件实现示例

A2UIButton.vue:

<template>
  <button @click="handleClick" class="a2ui-button">
    <A2UIText :component="childComponent" v-if="childComponent" />
  </button>
</template>

<script setup>
const handleClick = () => {
  // 收集表单数据
  const formData = {}
  // ...

  // 发送动作事件
  emit('action', {
    name: action.value.name,
    context: action.value.context || [],
    formData: formData
  })
}
</script>

3. 主应用组件

App.vue 核心逻辑:

<script setup>
import { ref } from 'vue'
import A2UIRenderer from './A2UIRenderer.vue'

const surfaces = ref([])

// 处理 A2UI 消息
const processA2UIMessages = (messages) => {
  messages.forEach(msg => {
    if (msg.beginRendering) {
      // 创建新的 surface
      const surface = {
        id: msg.beginRendering.surfaceId,
        root: msg.beginRendering.root,
        components: {},
        dataModel: {}
      }
      surfaces.value.push(surface)
    }

    if (msg.surfaceUpdate) {
      // 更新组件
      const surface = surfaces.value.find(
        s => s.id === msg.surfaceUpdate.surfaceId
      )
      if (surface) {
        msg.surfaceUpdate.components?.forEach(comp => {
          surface.components[comp.id] = comp
        })
      }
    }

    if (msg.dataModelUpdate) {
      // 更新数据模型
      // ...
    }
  })
}

// 发送消息到后端
const sendMessage = async () => {
  const response = await fetch('/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ message: userInput.value })
  })

  const data = await response.json()
  processA2UIMessages(data.a2ui_messages)
}
</script>

4. 启动前端服务

cd frontend
npm run dev

成功启动后:

  VITE v5.x.x  ready in xxx ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose

🚀 运行演示

1. 启动完整应用

终端 1 - 启动后端:

cd backend
python app.py

终端 2 - 启动前端:

cd frontend
npm run dev

2. 访问应用

打开浏览器,访问:http://localhost:5173

3. 使用示例

示例 1:查询天气

  1. 输入消息:"今天杭州天气怎么样?"
  2. 点击发送
  3. 智能体返回:生成一个表单界面,包含:
  4. 点击查询按钮:显示天气结果卡片
  5. 修改城市:在输入框中输入其他城市,点击"重新查询"

img_1.jpg

示例 2:直接查询

输入:"查询北京天气"

智能体会直接调用 MCP 服务查询天气,并显示结果界面。

img_2.jpg

4. 完整交互流程

第一阶段:用户发起查询

步骤 1-3:用户输入和请求发送

用户在 Vue 前端输入:"今天杭州天气怎么样?",前端发送POST /api/chat请求到 FastAPI 后端,请求体包含用户消息。

步骤 4-6:智能体处理

FastAPI 后端接收请求后,调用 LangChain 智能体。智能体将用户消息转发给通义千问 LLM,LLM 分析用户意图:

  • 识别用户想查询天气
  • 提取城市名称:杭州
  • 返回结构化输出:{ action: "show_result", city: "杭州", ... }

步骤 7-11:工具调用和数据获取

LangChain 智能体判断需要调用工具,执行query_weather("杭州")。MCP 工具层通过 HTTP 调用高德天气 API,获取杭州的实时天气数据(温度、天气状况、湿度、风速等),然后将数据返回给 LangChain。

步骤 12-14:生成 A2UI 响应

LangChain 根据天气数据生成完整的WeatherAgentOutput,包含所有天气信息。FastAPI 将输出转换为 A2UI JSON 格式,包括三种消息:

  • beginRendering- 创建 Surface
  • surfaceUpdate- 定义组件树
  • dataModelUpdate- 填充天气数据

步骤 15-17:前端渲染

Vue 前端接收 A2UI 消息,按顺序处理:

  1. 创建weather-resultSurface
  2. 解析组件定义,构建组件树
  3. 绑定数据模型,渲染组件
  4. 用户看到完整的天气结果界面(卡片、温度、湿度、风速等)

第二阶段:用户交互更新

步骤 1-4:用户修改和触发动作

用户在输入框中将城市修改为"北京",然后点击"重新查询"按钮。Vue 前端收集表单数据(包括修改后的城市名称),触发action事件,并发送POST /api/action请求。

步骤 5-9:后端处理和新数据获取

FastAPI 后端处理 action,识别是refresh_weather动作,从formData中提取城市名称"北京"。直接调用 MCP 服务查询北京天气,高德 API 返回北京天气数据。后端生成新的 A2UI JSON,包含更新后的天气信息。

步骤 10-11:增量更新界面

Vue 前端接收新的 A2UI 消息,执行增量更新:

  1. 更新dataModel中的数据(城市、温度、天气状况等)
  2. 触发响应式更新,相关组件自动刷新
  3. 用户看到更新后的北京天气信息

这种设计实现了完整的闭环交互:用户输入 → 智能体处理 → 工具调用 → 界面生成 → 用户交互 → 增量更新,整个过程流畅自然。

🔍 关键技术点解析

1. LangChain 工具调用

LangChain 的Tool机制让我们可以轻松地将函数转换为 LLM 可调用的工具:

def query_weather(city: str) -> str:
    """查询天气的工具函数"""
    weather = get_weather_data_via_mcp(city)
    return json.dumps(weather, ensure_ascii=False)

weather_tool = Tool(
    name="query_weather",
    func=query_weather,
    description="查询指定城市的天气信息..."
)

LLM 会根据用户意图自动决定是否调用这个工具。

2. A2UI 核心概念详解

2.1 Surface(界面区域)概念

什么是 Surface?

Surface 是 A2UI 中的核心概念,代表一个独立的界面区域。一个应用可以包含多个 Surface,每个 Surface 都有唯一的 ID。

为什么使用 Surface?

  • 隔离性:不同的 Surface 相互独立,可以同时存在多个界面区域
  • 可更新性:可以单独更新某个 Surface,不影响其他 Surface
  • 灵活性:智能体可以动态创建、更新、删除 Surface

实际应用:

在我们的天气查询项目中,我们使用了两个 Surface:

  • weather-form- 天气查询表单 Surface
  • weather-result- 天气结果显示 Surface
{
  "beginRendering": {
    "surfaceId": "weather-form",  // Surface 的唯一标识
    "root": "root-column",        // 根组件的 ID
    "styles": {
      "primaryColor": "#00BFFF",
      "font": "Roboto"
    }
  }
}

2.2 扁平结构 + 组件 ID 引用机制

扁平结构设计

A2UI 采用扁平化的组件列表,而不是传统的嵌套树结构:

{
  "surfaceUpdate": {
    "surfaceId": "weather-form",
    "components": [
      {
        "id": "root-column",      // 组件 ID
        "component": {
          "Column": {
            "children": {
              "explicitList": ["title-text", "form-column"]  // 通过 ID 引用子组件
            }
          }
        }
      },
      {
        "id": "title-text",       // 独立的组件定义
        "component": {
          "Text": {
            "text": {"literalString": "🌤️ 天气查询"}
          }
        }
      },
      {
        "id": "form-column",      // 另一个独立的组件
        "component": {
          "Column": {
            "children": {
              "explicitList": ["city-input", "submit-button"]
            }
          }
        }
      }
    ]
  }
}

为什么采用扁平结构?

  1. LLM 友好:大语言模型更容易生成扁平列表,而不是复杂的嵌套结构
  2. 增量更新:可以只更新部分组件,不需要重新生成整个树
  3. 局部更新:修改一个组件时,只需要发送该组件的更新消息
  4. 易于解析:客户端可以快速通过 ID 查找组件

组件引用机制:

组件之间通过 ID 建立关系,而不是嵌套定义:

{
  "id": "submit-button",
"component": {
    "Button": {
      "child": "submit-button-text"// 通过 ID 引用子组件
    }
  }
},
{
"id": "submit-button-text",        // 子组件独立定义
"component": {
    "Text": {
      "text": {"literalString": "查询天气"}
    }
  }
}

这种设计让组件可以复用重组,同一个文本组件可以被多个按钮引用。

2.3 数据绑定的 path 机制

两种数据绑定方式:

A2UI 支持两种数据绑定方式:

  1. 字面量(literalString):直接指定值
{
  "Text": {
    "text": {"literalString": "🌤️ 天气查询"}  // 固定文本
  }
}
  1. 路径绑定(path):绑定到数据模型
{
  "TextField": {
    "text": {
      "path": "/city"  // 绑定到数据模型的 /city 路径
    }
  }
}

数据模型更新:

通过dataModelUpdate消息更新数据模型:

{
  "dataModelUpdate": {
    "surfaceId": "weather-form",
    "path": "/",                    // 根路径
    "contents": [
      {
        "key": "city",              // 数据键
        "valueString": "杭州"       // 数据值
      }
    ]
  }
}

数据绑定工作原理:

  1. 组件定义时使用{"path": "/city"}绑定到数据模型
  2. 当数据模型更新时,所有绑定到该路径的组件自动更新
  3. 用户输入时,前端更新数据模型,组件自动反映变化

实际示例:

// 1. 定义组件(绑定到 /city)
{
"id": "city-input",
"component": {
    "TextField": {
      "text": {"path": "/city"}  // 绑定到数据模型
    }
  }
}

// 2. 更新数据模型
{
"dataModelUpdate": {
    "surfaceId": "weather-form",
    "contents": [
      {"key": "city", "valueString": "北京"}
    ]
  }
}

// 3. 前端自动更新:TextField 显示 "北京"

2.4 组件 child 引用机制

组件引用方式:

A2UI 组件通过childchildren属性引用子组件:

单个子组件(child):

{
  "id": "submit-button",
"component": {
    "Button": {
      "child": "submit-button-text"// 引用单个子组件
    }
  }
},
{
"id": "submit-button-text",
"component": {
    "Text": {
      "text": {"literalString": "查询天气"}
    }
  }
}

多个子组件(children):

{
  "id": "root-column",
  "component": {
    "Column": {
      "children": {
        "explicitList": ["title-text", "form-column"]  // 引用多个子组件
      }
    }
  }
}

为什么分离定义?

  1. 组件复用:同一个文本组件可以被多个按钮使用
  2. 灵活组合:可以动态改变组件的子组件
  3. 易于更新:更新子组件时不影响父组件
  4. LLM 友好:LLM 可以逐步生成组件,先定义子组件,再定义父组件

实际渲染流程:

1. 渲染 Button 组件
   ↓
2. 发现 child: "submit-button-text"
   ↓
3. 查找 ID 为 "submit-button-text" 的组件
   ↓
4. 渲染 Text 组件
   ↓
5. 显示 "查询天气"

2.5 完整的 A2UI JSON 消息示例

让我们看一个完整的 A2UI 消息序列,展示如何生成天气查询表单:

消息 1:开始渲染

{
  "beginRendering": {
    "surfaceId": "weather-form",
    "root": "root-column",
    "styles": {
      "primaryColor": "#00BFFF",
      "font": "Roboto"
    }
  }
}

这告诉前端:准备渲染一个名为weather-form的 Surface,根组件是root-column

消息 2:定义组件树

{
  "surfaceUpdate": {
    "surfaceId": "weather-form",
    "components": [
      {
        "id": "root-column",
        "component": {
          "Column": {
            "children": {
              "explicitList": [
                "title-text",
                "form-column"
              ]
            },
            "alignment": "center",
            "distribution": "start"
          }
        }
      },
      {
        "id": "title-text",
        "component": {
          "Text": {
            "text": {
              "literalString": "🌤️ 天气查询"
            },
            "usageHint": "h1"
          }
        }
      },
      {
        "id": "form-column",
        "component": {
          "Column": {
            "children": {
              "explicitList": [
                "city-input",
                "submit-button"
              ]
            },
            "alignment": "stretch"
          }
        }
      },
      {
        "id": "city-input",
        "component": {
          "TextField": {
            "label": {
              "literalString": "城市名称"
            },
            "text": {
              "path": "/city"
            },
            "textFieldType": "shortText"
          }
        }
      },
      {
        "id": "submit-button",
        "component": {
          "Button": {
            "child": "submit-button-text",
            "action": {
              "name": "submit_weather_form",
              "context": []
            }
          }
        }
      },
      {
        "id": "submit-button-text",
        "component": {
          "Text": {
            "text": {
              "literalString": "查询天气"
            }
          }
        }
      }
    ]
  }
}

这定义了完整的组件树,所有组件都是扁平列表,通过 ID 引用建立关系。

消息 3:填充数据模型

{
  "dataModelUpdate": {
    "surfaceId": "weather-form",
    "path": "/",
    "contents": [
      {
        "key": "city",
        "valueString": "北京"
      }
    ]
  }
}

这填充了数据模型,绑定到{"path": "/city"}的 TextField 会自动显示 "北京"。

消息处理顺序:

  1. 先发送beginRendering- 创建 Surface
  2. 再发送surfaceUpdate- 定义组件
  3. 最后发送dataModelUpdate- 填充数据

前端按顺序处理,确保在渲染时所有信息都已准备好。

3. A2UI 消息处理

A2UI 使用三种核心消息类型:

  1. beginRendering- 开始渲染一个新的界面区域
  2. surfaceUpdate- 更新组件定义
  3. dataModelUpdate- 更新数据模型

这种设计支持增量更新,用户体验更流畅。

4. Vue 3 响应式系统

使用 Vue 3 的 Composition API 和响应式系统:

<script setup>
import { ref, reactive, computed } from 'vue'

const surfaces = ref([])  // 响应式数组
const dataModel = reactive({})  // 响应式对象

// 自动追踪依赖,自动更新 UI
</script>

5. MCP 协议集成

MCP(Model Context Protocol)让我们可以通过标准协议调用外部工具:

  • HTTP 方式:适合生产环境
  • SSE 方式:支持服务器推送
  • stdio 方式:适合本地开发

代码会自动检测并选择合适的连接方式。

📊 项目特色功能

1. 智能意图识别

智能体能理解多种表达方式:

  • "今天北京天气怎么样?"
  • "查询杭州天气"
  • "我想知道上海的天气"
  • "北京温度多少?"

2. 自动工具调用

智能体自动判断是否需要查询天气:

  • 如果用户明确提到城市和"天气",直接查询
  • 如果只是询问天气,显示表单让用户选择城市

3. 交互式界面

不是简单的文本回复,而是完整的 UI:

  • 表单输入
  • 按钮交互
  • 数据展示卡片
  • 实时更新

4. 错误处理

完善的错误处理和回退机制:

  • MCP 调用失败 → 自动使用模拟数据
  • LLM 调用失败 → 使用规则模式
  • 网络错误 → 友好提示

🎓 扩展建议

1. 添加更多 A2UI 组件

当前实现了基础组件,可以扩展:

  • Image(图片)
  • Chart(图表)
  • List(列表)
  • Dialog(对话框)

2. 增强智能体能力

  • 添加对话历史管理
  • 支持多轮对话
  • 添加更多工具(如天气预报、历史天气等)

3. 优化用户体验

  • 添加加载动画
  • 添加错误重试机制
  • 优化移动端适配

4. 部署到生产环境

  • 使用 Docker 容器化
  • 配置 Nginx 反向代理
  • 添加日志和监控

📝 总结

通过这个完整的实战项目,我们实现了:

LangChain 智能体- 使用 LangChain 框架构建智能体 ✅MCP 工具集成- 通过 MCP 协议调用高德天气 API ✅A2UI 界面生成- 生成交互式用户界面 ✅Vue 3 前端渲染- 使用 Vue 3 渲染 A2UI 组件

这个项目展示了现代 AI 应用开发的完整链路:

  1. 智能体层- LangChain + LLM
  2. 工具层- MCP 协议
  3. 界面层- A2UI 协议
  4. 渲染层- Vue 3 框架

核心价值

这个架构的优势在于:

  • 🔄解耦合- 各层独立,易于维护
  • 🚀可扩展- 轻松添加新功能和工具
  • 🎨灵活性- 支持多种 LLM 和前端框架
  • 🔒安全性- A2UI 保证 UI 生成的安全性

下一步

现在你已经掌握了:

  • A2UI 的基本概念和用法
  • LangChain 智能体的构建方法
  • MCP 工具的集成方式
  • Vue 3 组件的开发

可以尝试:

  • 创建自己的智能体应用
  • 集成其他外部工具
  • 扩展 A2UI 组件库
  • 优化用户体验

📚 参考资源

  • A2UI 官方仓库
  • LangChain 文档
  • MCP 协议文档
  • Vue 3 文档
  • 高德开放平台

💡获取完整代码:如果需要获取完整的源代码、配置文件和详细文档,欢迎在评论区留言,我会私发给你!

代码包括:

  • ✅ 完整的后端代码(LangChain + MCP + A2UI)
  • ✅ 完整的前端代码(Vue 3 + A2UI 渲染器)
  • ✅ 配置文件和环境变量示例
  • ✅ 详细的 README 文档

🎉END

如果你觉得本文有帮助,欢迎点赞👍、在看👀、转发📤,也欢迎留言💬分享你的经验!