架构
我们将利用与 [旅行规划器]
相同的架构,并在此底子上扩展 A2A + MCP 协议。
下面的演示只是为了说明 A2A 协议在多个署理之间的通信,仅用于说明目的。
上面的架构利用了模块化的多署理 AI 系统,其中每个署理都是独立可摆设的,并且通过谷歌的A2A(Agent-to-Agent)协议进行通信。
核心组件
- 用户界面层 —— 向前端服务器发送 HTTP 哀求
- 署理层 —— 和谐宿主署理、署理 1 和署理 2 之间的交互
- 协议层 —— 署理之间通过 A2A 协议进行通信
- 外部数据层 —— 利用 MCP 访问外部 API
署理脚色:
- 行程规划署理 —— 作为中心和谐者 —— 宿主署理,和谐用户与专业署理之间的交互。
- 航班搜索署理 —— 一个专门负责根据用户输入获取航班选项的署理
- 旅店搜索署理 —— 一个专门负责根据用户偏好获取旅店过夜的署理
MCP 在本项目中的实现:
航班搜索 MCP 服务器
- 连接:航班搜索(署理 1)连接到 MCP 航班服务器
- 功能:连接到航班预订 API 和数据库
旅店搜索 MCP 服务器
- 连接:旅店搜索(署理 2)连接到 MCP 旅店服务器
- 功能:连接到旅店预订系统和聚合器
署理通信流程
以下是利用 Mermaid 语法绘制的流程图,形貌了用户通过 Streamlit UI 提交旅行查询并天生完备行程的过程:
流程图说明:
- 用户通过 Streamlit UI 提交旅行查询:用户在前端界面输入旅行需求。
- 旅行规划器解析查询以提取关键信息:旅行规划器解析用户输入,提取出发地、目的地、日期等关键信息。
- 旅行规划器向航班搜索署理哀求航班信息:旅行规划器将关键信息发送给航班搜索署理。
- 航班搜索署理通过调用 MCP 服务器返回可用航班:航班搜索署理查询航班信息并返回结果。
- 旅行规划器提取目的地详细信息:旅行规划器从航班信息中提取目的地相干细节。
- 旅行规划器向旅店搜索署理哀求旅店信息:旅行规划器将目的地信息发送给旅店搜索署理。
- 旅店搜索署理返回过夜选项:旅店搜索署理查询旅店信息并返回结果。
- 旅行规划器将所有数据综合成一个完备的行程:旅行规划器整合航班和旅店信息,天生完备的行程。
这个流程图清晰地展示了从用户输入到天生完备行程的整个过程。
实现
让我们深入相识一下如何利用 ADK + MCP + Gemini AI 构建这个多署理系统,我们将把它分解为关键的实现步调。
准备工作
2. 获取谷歌 Gemini 天生式 AI 的 API 密钥
3. 获取有效的 SerpAPI 密钥
4. 获取有效的 OpenAI GPT 密钥
项目文件布局
- ├── common
- │ ├── __init__.py
- │ ├── client
- │ │ ├── __init__.py
- │ │ ├── card_resolver.py
- │ │ └── client.py
- │ ├── server
- │ │ ├── __init__.py
- │ │ ├── server.py
- │ │ ├── task_manager.py
- │ │ └── utils.py
- │ ├── types.py
- │ └── utils
- │ ├── in_memory_cache.py
- │ └── push_notification_auth.py
- ├── flight_search_app
- │ ├── a2a_agent_card.json
- │ ├── agent.py
- │ ├── main.py
- │ ├── static
- │ │ └── .well-known
- │ │ └── agent.json
- │ └── streamlit_ui.py
- ├── hotel_search_app
- │ ├── README.md
- │ ├── a2a_agent_card.json
- │ ├── langchain_agent.py
- │ ├── langchain_server.py
- │ ├── langchain_streamlit.py
- │ ├── static
- │ │ └── .well-known
- │ │ └── agent.json
- │ └── streamlit_ui.py
- └── itinerary_planner
- ├── __init__.py
- ├── a2a
- │ ├── __init__.py
- │ └── a2a_client.py
- ├── a2a_agent_card.json
- ├── event_log.py
- ├── itinerary_agent.py
- ├── itinerary_server.py
- ├── run_all.py
- ├── static
- │ └── .well-known
- │ └── agent.json
- └── streamlit_ui.py
复制代码 第一步:设置虚拟环境
安装依赖项
- # 设置虚拟环境
- python -m venv .venv # 激活虚拟环境
- source .venv/bin/activate# 安装依赖项
- pip install fastapi uvicorn streamlit httpx python-dotenv pydantic
- pip install google-generativeai google-adk langchain langchain-openai
复制代码 第二步:安装 MCP 服务器包
mcp 旅店服务器 — https://pypi.org/project/mcp-hotel-search/
mcp 航班服务器 — https://pypi.org/project/mcp-flight-search/
- # 安装 mcp 酒店搜索
- pip install mcp-hotel-search# 安装 mcp 航班搜索
- pip install mcp-flight-search
复制代码 第三步:设置 Gemini、OpenAI、SerpAI 的环境变量
设置上面准备工作中提到的环境变量
- GOOGLE_API_KEY=your_google_api_key
- OPENAI_API_KEY=your_openai_api_key
- SERP_API_KEY=your_serp_api_key
复制代码 第四步:利用 ADK 设置航班搜索(署理)作为 MCP 客户端,利用 Gemini 2.0 Flash
利用https://github.com/google/A2A/tree/main/samples/python/common 中的可复用模块。
- ├── common/ # 共享 A2A 协议组件
- │ ├── __init__.py
- │ ├── client/ # 客户端实现
- │ │ ├── __init__.py
- │ │ └── client.py # 基础 A2A 客户端
- │ ├── server/ # 服务器实现
- │ │ ├── __init__.py
- │ │ ├── server.py # A2A 服务器实现
- │ │ └── task_manager.py # 任务管理工具
- │ └── types.py # A2A 共享类型定义
复制代码
- ├── flight_search_app/ # 航班搜索代理(代理 1)
- │ ├── __init__.py
- │ ├── a2a_agent_card.json # 代理能力声明
- │ ├── agent.py # ADK 代理实现
- │ ├── main.py # ADK 服务器入口点,使用 Gemini LLM
- │ └── static/ # 静态文件
- │ └── .well-known/ # 代理发现目录
- │ └── agent.json # 标准化代理发现文件
复制代码 4.1 利用 ADK 署理实现作为 MCP 客户端从 MCP 服务器获取工具
- from google.adk.agents.llm_agent import LlmAgent
- from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, StdioServerParameters..
- ..
- # 从 MCP 服务器获取工具
- server_params = StdioServerParameters(
- command="mcp-flight-search",
- args=["--connection_type", "stdio"],
- env={"SERP_API_KEY": serp_api_key},) tools, exit_stack = await MCPToolset.from_server(
- connection_params=server_params)
- ..
- ..
复制代码 4.2 利用通用 A2A 服务器组件和类型以及谷歌 ADK 运行器、会话和署理定义 ADK 服务器入口点
- from google.adk.runners import Runner
- from google.adk.sessions import InMemorySessionService
- from google.adk.agents import Agent
- from .agent import get_agent_async# 导入通用 A2A 服务器组件和类型
- from common.server.server import A2AServer
- from common.server.task_manager import InMemoryTaskManager
- from common.types import (
- AgentCard,
- SendTaskRequest,
- SendTaskResponse,
- Task,
- TaskStatus,
- Message,
- TextPart,
- TaskState,
- )# --- 自定义航班搜索任务管理器 ---
- class FlightAgentTaskManager(InMemoryTaskManager):
- """特定于 ADK 航班搜索代理的任务管理器。"""
- def __init__(self, agent: Agent, runner: Runner, session_service: InMemorySessionService):
- super().__init__()
- self.agent = agent
- self.runner = runner
- self.session_service = session_service
- logger.info("FlightAgentTaskManager 初始化完成。")...
- ...
复制代码 4.3 利用署理卡片创建 A2A 服务器实例
- # --- 主执行块 ---
- async def run_server():
- """初始化服务并启动航班搜索 A2A 服务器。"""
- logger.info("开始航班搜索 A2A 服务器初始化...") session_service = None
- exit_stack = None
- try:
- session_service = InMemorySessionService()
- agent, exit_stack = await get_agent_async()
- runner = Runner(
- app_name='flight_search_a2a_app',
- agent=agent,
- session_service=session_service,
- ) # 创建特定的任务管理器
- task_manager = FlightAgentTaskManager(
- agent=agent,
- runner=runner,
- session_service=session_service
- ) # 定义代理卡片
- port = int(os.getenv("PORT", "8000"))
- host = os.getenv("HOST", "localhost")
- listen_host = "0.0.0.0" agent_card = AgentCard(
- name="Flight Search Agent (A2A)",
- description="根据用户查询提供航班信息。",
- url=f"http://{host}:{port}/",
- version="1.0.0",
- defaultInputModes=["text"],
- defaultOutputModes=["text"],
- capabilities={"streaming": False},
- skills=[
- {
- "id": "search_flights",
- "name": "Search Flights",
- "description": "根据出发地、目的地和日期搜索航班。",
- "tags": ["flights", "travel"],
- "examples": ["Find flights from JFK to LAX tomorrow"]
- }
- ]
- ) # 创建 A2AServer 实例
- a2a_server = A2AServer(
- agent_card=agent_card,
- task_manager=task_manager,
- host=listen_host,
- port=port
- ) # 配置 Uvicorn
- config = uvicorn.Config(
- app=a2a_server.app, # 传递 A2AServer 的 Starlette 应用
- host=listen_host,
- port=port,
- log_level="info"
- )
- server = uvicorn.Server(config)
- ...
- ...
复制代码 4.4 让我们启动航班搜索应用
第五步:利用 LangChain 配置旅店搜索署理作为 MCP 客户端,并利用 OpenAI(GPT-4o)作为 LLM
- ├── hotel_search_app/ # 酒店搜索代理(代理 2)
- │ ├── __init__.py
- │ ├── a2a_agent_card.json # 代理能力声明
- │ ├── langchain_agent.py # LangChain 代理实现
- │ ├── langchain_server.py # 服务器入口点
- │ └── static/ # 静态文件
- │ └── .well-known/ # 代理发现目录
- │ └── agent.json # 标准化代理发现文件
复制代码
图片由作者提供
5.1. LangChain 署理 实现作为 MCP 客户端,利用 OpenAI LLM 作为语言模子
- from langchain_openai import ChatOpenAI
- from langchain.agents import AgentExecutor, create_openai_functions_agent
- from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
- from langchain_mcp_adapters.client import MultiServerMCPClient# MCP 客户端配置
- MCP_CONFIG = {
- "hotel_search": {
- "command": "mcp-hotel-search",
- "args": ["--connection_type", "stdio"],
- "transport": "stdio",
- "env": {"SERP_API_KEY": os.getenv("SERP_API_KEY")},
- }
- }class HotelSearchAgent:
- """使用 LangChain MCP 适配器的酒店搜索代理。""" def __init__(self):
- self.llm = ChatOpenAI(model="gpt-4o", temperature=0) def _create_prompt(self):
- """创建一个带有自定义系统消息的提示模板。"""
- system_message = """你是一个有用的酒店搜索助手。
- """ return ChatPromptTemplate.from_messages([
- ("system", system_message),
- ("human", "{input}"),
- MessagesPlaceholder(variable_name="agent_scratchpad"),
- ])
- ..
- ..
- async def process_query(self, query):
- ... # 为这个查询创建一个 MCP 客户端实例
- async with MultiServerMCPClient(MCP_CONFIG) as client:
- # 从这个客户端实例获取工具
- tools = client.get_tools() # 创建一个提示
- prompt = self._create_prompt() # 使用这些工具创建一个代理
- agent = create_openai_functions_agent(
- llm=self.llm,
- tools=tools,
- prompt=prompt
- ) # 使用这些工具创建一个执行器
- executor = AgentExecutor(
- agent=agent,
- tools=tools,
- verbose=True,
- handle_parsing_errors=True,
- )
复制代码 5.2 利用通用 A2A 服务器组件和类型创建 A2AServer 实例
- # 直接使用底层代理
- from hotel_search_app.langchain_agent import get_agent, HotelSearchAgent # 导入通用 A2A 服务器组件和类型
- from common.server.server import A2AServer
- from common.server.task_manager import InMemoryTaskManager
- from common.types import (
- AgentCard,
- SendTaskRequest,
- SendTaskResponse,
- Task,
- TaskStatus,
- Message,
- TextPart,
- TaskState
- )
- ..
- ..class HotelAgentTaskManager(InMemoryTaskManager):
- """特定于酒店搜索代理的任务管理器。"""
- def __init__(self, agent: HotelSearchAgent):
- super().__init__()
- self.agent = agent # HotelSearchAgent 实例
- logger.info("HotelAgentTaskManager 初始化完成。") async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse:
- """通过调用代理的 process_query 处理 tasks/send 请求。"""
- task_params = request.params
- task_id = task_params.id
- user_message_text = None logger.info(f"HotelAgentTaskManager 正在处理任务 {task_id}")# --- 主执行块 ---
- async def run_server():
- """初始化服务并启动酒店搜索 A2A 服务器。"""
- logger.info("开始酒店搜索 A2A 服务器初始化...") agent_instance: Optional[HotelSearchAgent] = None
- try:
- agent_instance = await get_agent()
- if not agent_instance:
- raise RuntimeError("初始化 HotelSearchAgent 失败") # 创建特定的任务管理器
- task_manager = HotelAgentTaskManager(agent=agent_instance) # 定义代理卡片
- port = int(os.getenv("PORT", "8003")) # 默认端口 8003
- host = os.getenv("HOST", "localhost")
- listen_host = "0.0.0.0" agent_card = AgentCard(
- name="Hotel Search Agent (A2A)",
- description="根据位置、入住/退房日期和客人数量提供酒店信息。",
- url=f"http://{host}:{port}/",
- version="1.0.0",
- defaultInputModes=["text"],
- defaultOutputModes=["text"],
- capabilities={"streaming": False},
- skills=[
- {
- "id": "search_hotels",
- "name": "Search Hotels",
- "description": "根据位置、入住/退房日期和客人数量搜索酒店。",
- "tags": ["hotels", "travel", "accommodation"],
- "examples": ["Find hotels in London from July 1st to July 5th for 2 adults"]
- }
- ]
- ) # 创建 A2AServer 实例,不使用 endpoint 参数
- a2a_server = A2AServer(
- agent_card=agent_card,
- task_manager=task_manager,
- host=listen_host,
- port=port
- ) config = uvicorn.Config(
- app=a2a_server.app, # 传递 A2AServer 的 Starlette 应用
- host=listen_host,
- port=port,
- log_level="info"
- )
复制代码 5.3 让我们启动旅店搜索应用(Langchain)作为入口点以调用 MCP 服务器
第六步:实现宿主署理作为署理之间的和谐者,利用 A2A 协议
行程规划器是上述旅行规划的核心组件,利用 A2A 协议与航班和旅店服务进行通信。
- ├── itinerary_planner/ # 行程规划宿主代理(代理 3)
- │ ├── __init__.py
- │ ├── a2a/ # A2A 客户端实现
- │ │ ├── __init__.py
- │ │ └── a2a_client.py # 航班和酒店代理的客户端
- │ ├── a2a_agent_card.json # 代理能力声明
- │ ├── event_log.py # 事件日志工具
- │ ├── itinerary_agent.py # 主规划器实现
- │ ├── itinerary_server.py # FastAPI 服务器
- │ ├── run_all.py # 运行所有组件的脚本
- │ ├── static/ # 静态文件
- │ │ └── .well-known/ # 代理发现目录
- │ │ └── agent.json # 标准化代理发现文件
- │ └── streamlit_ui.py # 主用户界面
复制代码 6.1 利用航班和旅店 API URL 实现 A2A 协议
- 包罗与服务通信的客户端代码
- 实现 Agent-to-Agent 协议
- 包罗调用航班和旅店搜索服务的模块
- # A2A 兼容代理 API 的根端点
- FLIGHT_SEARCH_API_URL = os.getenv("FLIGHT_SEARCH_API_URL", "http://localhost:8000")
- HOTEL_SEARCH_API_URL = os.getenv("HOTEL_SEARCH_API_URL", "http://localhost:8003")class A2AClientBase:
- """通过根端点与 A2A 兼容代理通信的基础客户端。""" async def send_a2a_task(self, user_message: str, task_id: Optional[str] = None, agent_type: str = "generic") -> Dict[str, Any]:
- ...
- ....
- # 构造带有 A2A 方法和修正后的参数结构的 JSON-RPC 负载
- payload = {
- "jsonrpc": "2.0",
- "method": "tasks/send",
- "params": {
- "id": task_id,
- "taskId": task_id,
- "message": {
- "role": "user",
- "parts": [
- {"type": "text", "text": user_message}
- ]
- }
- },
- "id": task_id
- }
复制代码 6.2 行程规划署理卡片
JSON 元数据文件,形貌署理的本领、端点、认证要求和技能。在 A2A 协议中用于服务发现。
- {
- "name": "Travel Itinerary Planner",
- "displayName": "Travel Itinerary Planner",
- "description": "一个协调航班和酒店信息以创建综合旅行行程的代理",
- "version": "1.0.0",
- "contact": "code.aicloudlab@gmail.com",
- "endpointUrl": "http://localhost:8005",
- "authentication": {
- "type": "none"
- },
- "capabilities": ["streaming"],
- "skills": [
- {
- "name": "createItinerary",
- "description": "创建包含航班和住宿的综合旅行行程",
- "inputs": [
- {
- "name": "origin",
- "type": "string",
- "description": "出发城市或机场代码"
- },
- {
- "name": "destination",
- "type": "string",
- "description": "目的地城市或地区"
- },
- {
- "name": "departureDate",
- "type": "string",
- "description": "出发日期,格式为 YYYY-MM-DD"
- },
- {
- "name": "returnDate",
- "type": "string",
- "description": "返回日期,格式为 YYYY-MM-DD(可选)"
- },
- {
- "name": "travelers",
- "type": "integer",
- "description": "旅行者人数"
- },
- {
- "name": "preferences",
- "type": "object",
- "description": "其他偏好,如预算、酒店设施等"
- }
- ],
- "outputs": [
- {
- "name": "itinerary",
- "type": "object",
- "description": "包含航班、酒店和行程的完整旅行行程"
- }
- ]
- }
- ]
- }
复制代码 6.3 利用谷歌天生式 AI SDK 的行程署理
为了简化演示,这里利用了 GenAI SDK(也可以利用 ADK、CrewAI 或其他框架)
行程署理 是系统的中心宿主署理,它和谐与航班和旅店搜索服务的通信,并利用语言模子解析自然语言哀求。
- import google.generativeai as genai # 直接使用 SDK
- ..
- ..
- from itinerary_planner.a2a.a2a_client import FlightSearchClient, HotelSearchClient# 配置谷歌生成式 AI SDK
- genai.configure(api_key=api_key)class ItineraryPlanner:
- """一个使用谷歌生成式 AI SDK 协调航班和酒店搜索代理以创建行程的规划器。""" def __init__(self):
- """初始化行程规划器。"""
- logger.info("使用谷歌生成式 AI SDK 初始化行程规划器")
- self.flight_client = FlightSearchClient()
- self.hotel_client = HotelSearchClient() # 使用 SDK 创建 Gemini 模型实例
- self.model = genai.GenerativeModel(
- model_name="gemini-2.0-flash",
- )
- ..
- ..
复制代码 6.4 行程服务器 —— FastAPI 服务器,暴露行程规划器的端点,处理传入的 HTTP 哀求并将哀求路由到行程署理
- from fastapi import FastAPI, HTTPException, Requestfrom itinerary_planner.itinerary_agent import ItineraryPlanner@app.post("/v1/tasks/send")
- async def send_task(request: TaskRequest):
- """处理 A2A tasks/send 请求。"""
- global planner if not planner:
- raise HTTPException(status_code=503, detail="规划器未初始化") try:
- task_id = request.taskId # 提取用户的消息
- user_message = None
- for part in request.message.get("parts", []):
- if "text" in part:
- user_message = part["text"]
- break if not user_message:
- raise HTTPException(status_code=400, detail="请求中未找到文本消息") # 根据查询生成行程
- itinerary = await planner.create_itinerary(user_message) # 创建 A2A 响应
- response = {
- "task": {
- "taskId": task_id,
- "state": "completed",
- "messages": [
- {
- "role": "user",
- "parts": [{"text": user_message}]
- },
- {
- "role": "agent",
- "parts": [{"text": itinerary}]
- }
- ],
- "artifacts": []
- }
- } return response
复制代码 6.5 Streamlit_ui —— 利用 Streamlit 构建的用户界面,为旅行规划提供表单,并以用户友好的格式显示结果
- ...
- ...
- # API 端点
- API_URL = "http://localhost:8005/v1/tasks/send"def generate_itinerary(query: str):
- """向行程规划器 API 发送查询。"""
- try:
- task_id = "task-" + datetime.now().strftime("%Y%m%d%H%M%S") payload = {
- "taskId": task_id,
- "message": {
- "role": "user",
- "parts": [
- {
- "text": query
- }
- ]
- }
- } # 将用户查询和请求记录到事件日志
- log_user_query(query)
- log_itinerary_request(payload) response = requests.post(
- API_URL,
- json=payload,
- headers={"Content-Type": "application/json"}
- )
- response.raise_for_status() result = response.json() # 提取代理的响应消息
- agent_message = None
- for message in result.get("task", {}).get("messages", []):
- if message.get("role") == "agent":
- for part in message.get("parts", []):
- if "text" in part:
- agent_message = part["text"]
- break
- if agent_message:
- break
- ..
- ..
- ...
复制代码 第七步:最终演示
在每个终端中,按照以下方式启动服务器署理,正如下面的演示所看到的那样:
- # 启动航班搜索代理 - 1,端口 8000
- python -m flight_search_app.main# 启动酒店搜索代理 - 2,端口 8003
- python -m hotel_search_app.langchain_server# 启动行程宿主代理 - 端口 8005
- python -m itinerary_planner.itinerary_server# 启动前端 UI - 端口 8501
- streamlit run itinerary_planner/streamlit_ui.py
复制代码
航班搜索日志,显示从宿主代剃头起的任务 ID
旅店搜索日志,显示从宿主代剃头起的任务
行程规划器 —— 宿主署理,显示所有哀求/响应
署理事件日志
这个演示实现了 谷歌 A2A 协议 的核心原则,使署理能够以布局化、可互操作的方式进行通信。在上面的演示中实现的组件如下:
- 署理卡片 —— 所有署理都暴露了 .well-known/agent.json 文件以供发现。
- A2A 服务器 —— 每个署理都作为一个 A2A 服务器运行:flight_search_app、hotel_search_app 和 itinerary_planner
- A2A 客户端 —— 行程规划器包罗了针对航班和旅店署理的专用 A2A 客户端。
- 任务管理 —— 每个哀求/响应都被建模为一个 A2A 任务,状态包括已提交、正在处理和已完成。
- 消息布局 —— 利用标准的 JSON-RPC 格式,包罗脚色(用户/署理)和部分(重要是 TextPart)。
以下组件在我们的演示中尚未实现,但可以扩展为适用于企业级署理:
- 流式传输(SSE) —— A2A 支持服务器发送事件用于长时间运行的任务,但我们的演示利用的是简单的哀求/响应,耗时不到 3-5 秒。
- 推送关照 —— 尚未利用 Webhook 更新机制。
- 复杂部分 —— 只利用了 TextPart。可以添加 DataPart、FilePart 等以支持更丰富的负载。
- 高级发现 —— 实现了根本的 .well-known/agent.json,但尚未实现高级认证、JWKS 或授权流程。
A2A 协议 —— 官方文档
总结
在本文中,我们探索了如何利用可复用的 A2A 组件、ADK、LangChain 和 MCP 构建一个功能齐全的多署理系统,用于旅行规划场景。通过联合这些开源工具和框架,我们的署理能够做到以下几点:
- 利用 A2A 动态发现并调用彼此
- 通过 MCP 以模子友好的方式连接到外部 API
- 利用当代框架,如 ADK 和 LangChain
- 以异步方式通信,具有清晰的任务生命周期和布局化结果
同样的原理可以扩展到更多领域,如零售、客户服务主动化、操作工作流以及 AI 辅助的企业工具。
感谢Arjun Prabhulal的分享https://medium.com/ai-cloud-lab/building-multi-agent-ai-app-with-googles-a2a-agent2agent-protocol-adk-and-mcp-a-deep-a94de2237200
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |