使用Google Gen AI工具包和Neo4j知識(shí)圖譜構(gòu)建AI代理助手
Michael Hunger,Neo4j 产品创新负责人
Kurtis Van Gent,Google 高级软件工程师
想了解更多的朋友,可以观看2025年3月12日的Gen AI Toolbox + Neo4j 直播录像。
教你如何使用Google的Gen AI工具箱和Neo4j构建AI代理程序
简介.近日与LangChain合作推出的数据库的Gen AI工具箱现已包含Neo4j集成。
[数据库Gen AI工具箱现已开放公测。现在就来试试 | Google Cloud 博客数据库Gen AI工具箱现已开放公测:基于代理的生成式AI,现在就来试试。
cloud.google.com](https://cloud.google.com/blog/products/ai-machine-learning/announcing-gen-ai-toolbox-for-databases-get-started-today?e=48754805&hl=en&source=post_page-----86526659b46a---------------------------------------)
这种合作是在与Kurtis Van Gent在GraphRAG聚会之后的讨论中开始的,他在那次聚会上提议提升代理系统中数据库集成的基础设施,使之既作为数据源又作为工具。Neo4j的加入为Toolbox用户带来了知识图谱的能力,扩展了数据库管理和生成AI应用的功能范围。
虽然你可能已经听说过很多关于AI代理的事情,这里快速回顾一下它们与简单的检索增强生成(RAG)方法有何不同之处,以及这会带来哪些影响。
在传统的RAG流程或方法中,用户的问题不会直接由LLM解答,而是首先从可信的数据源检索出相关的信息,然后将这些信息连同问题一起提供给LLM作为回答参考。因此,LLM只用来发挥其语言能力,而不是依赖预训练的知识。这有助于提高回答的相关性和准确性,并且在一定程度上增加了可解释性。
使用GraphRAG,知识图谱作为数据源,远远超出了从向量存储检索文本片段的范畴。图中的关联信息提供了来自相关实体、文档和结构化摘要的相关上下文。由于检索到的信息具有结构化的特性,因此解释并透明地展示答案来源的详细信息也变得更加容易。
在代理架构式中,LLM 配备了一套信息检索和代表用户执行操作的工具。LLM 在分析问题并将其分解时扮演更积极的角色,制定计划,使用一系列工具来检索不同输入和请求结果的信息,然后执行此计划。在执行过程中,代理-LLM 可以按照顺序或循环多次调用工具,甚至通过将之前检索到的结果用作新工具调用的输入来进一步检索更复杂的信息。在运行代理循环期间,它观察收集的数据是否足以满足用户的需求,并应用诸如重新排名、判断和基于相关性选择等机制,并遵循一些安全规则。
代理架构 构建模块
通过对工具调用的结构化分析和参数提取,对向量相似度搜索的需求因此减少。总的来说,LLM代理的工作可以类比于数据库世界中的查询规划和执行过程。
提供的代理工具具有多种选项和形态,从在(Python)沙箱中运行代码到调用REST API接口,再到使用数据库检索信息或利用其他LLM,选择多样。大多数工具将用于信息检索或处理,而每个代理仅有少数工具可以进行更新和修改操作。
现在很多大型语言模型已经支持通过函数调用来调用工具了,允许你传递一个签名列表(包括名称、描述、参数和结果),然后模型会从这些签名中选择合适的,并提供执行所需的参数。代理框架则增加了许多额外的功能——比如安全防护、基于图的工作流程和执行内存以及其他提到的技术。
尽管代理是一种非常强大的机制(如下面的例子所示,令人惊讶的是,即使是在回答复杂问题和处理任务以及保持对话流畅方面也表现得非常出色),不过还是有一些挑战,比如:
- 工具配置和描述必须一致且足够详细,以便LLM能够做出正确的选择。
- 工具执行管理通常留给开发者负责,因此根据他们对数据基础设施的熟悉程度,资源管理往往不够理想。
- 数据分析师无法配置数据库查询和其他工具,因为需要提供大量额外工作(如OpenAPI规范、函数调用签名、Python执行代码)。
- 必须选择正确的工具,因此从名称和描述信息中必须非常明确每个工具的功能以及它适用于哪些子任务。
- 工具必须明确指定参数的种类和数据类型,以便LLM在传递参数给工具之前进行转换。
- 需要以一种防止代码注入的方式使用参数,以避免数据泄露或任意修改。
- 向量搜索在代理设置中不那么重要,因为在代理设置中很少会将用户问题作为短语传递给嵌入生成和向量相似性搜索,而是进行结构化的参数提取和特定工具调用。
当涉及到创建访问数据库的工具时,通常会遇到许多挑战,这些挑战可能很难用当今的编排框架来解决:认证、授权、数据清理处理、连接池管理等。这些挑战很快就会成为开发者的一大负担,拖慢开发进度,并在反复实现相同的样板代码时容易出错。这样一来,不仅减缓了开发进度,还为每次重复的实现留下了出错的空间。
欢迎来到 Toolbox — 这是一个开源服务器,可让构建与数据库交互的生成式 AI 工具更简单。它通过处理诸如连接池、认证等复杂问题,使您能够更快捷、更轻松和更安全地开发工具。Toolbox 允许您在一个地方定义工具,并通过少于三行代码即可与您的 LangGraph 代理集成:
从 toolbox_langchain 导入 ToolboxClient
# 将URL更新为你服务器的地址
client = ToolboxClient("http://127.0.0.1:5000")
# 这些工具可以传递给你的代理以供使用。
tools = await client.aload_toolset()
工具箱确确实是一个开源项目:作为发布的一部分,Google Cloud 与其他数据库供应商合作,包括 Neo4j,构建了对大量开源和专有数据库的支持。
- PostgreSQL(包括AlloyDB 和 MySQL 云数据库)
- MySQL(包括 MySQL 云数据库)
- SQL Server(包括 SQL Server 云数据库)
- Spanner
- Neo4j
我们也有一系列功能来帮助您的应用程序更快地投入生产环境——包括内置的终端用户认证支持以及通过OpenTelemetry实现的内置可观测性。
Neo4j Cypher: 代码和资源Google 添加到 Gen AI 工具箱中的 Neo4j 集成功能支持定义 Cypher 执行所需的数据源和工具。数据来源 表示如何连接到您的 Neo4j 实例,例如 URI、API 密钥等。具体操作 表示代理可以执行的具体操作,例如读取或写入数据。
Neo4j 是领先的开源图数据库——它不是将信息作为表格来管理,而是作为实体(节点)及其之间的关系来管理,从而允许灵活且强大的连接信息表示方式。图增加了生物医学、供应链管理、制造、欺诈检测和运输物流等多个领域的独特功能。知识图谱 可以被视为您组织(包括人员、流程、产品、合作伙伴等)的数字孪生,是 LLM 语言技能的强大“事实记忆”助手。
在获取Toolbox二进制文件后,你可以具体地通过在名为tools.yaml的文件中配置数据库连接详情(URL、用户名、密码)来为多个Neo4j 数据源提供定义。这个文件并在此时提供给Toolbox二进制文件。
这些工具或功能会与一个数据源(dataset)关联,你稍后会定义它们,在大多数情况下,查询都特定于该数据集的数据模型(data model)。
数据源:
my-neo4j-source:
类型: "neo4j"
uri: "neo4j+s://xxxx.databases.neo4j.io:7687"
用户名: "neo4j"
口令: "my-password"
数据库: "neo4j"
工具 是你想要对某个来源执行的具体动作。为了让这些工具能在代理设置中发挥作用,详细描述工具、参数和结果非常重要,这样LLM才能更好地理解其适用性。
工具中的陈述可以是通用的(比如通过谓词获取单个实体,获取一阶或二阶邻域,或者甚至找到实体之间的最短路径)。它们也可以是具体的,例如由专家编写,遵循数据中更复杂的一系列路径(例如,检索药物作用的基因或蛋白质,或为用户推荐产品)。工具还可以通过更新属性或添加表示业务概念的节点和关系来更新图数据。
但工具也可以利用高级功能,比如运行图算法进行聚类、进行空间操作,或对文本短语进行向量相似度或短语全文搜索。
以下是一个简单的neo4j-cypher工具示例,该工具通过演员和起始发行年份来获取一系列电影。请注意工具的描述及其参数,这能让LLM在每个规划和执行步骤中更轻松地进行判断并选择合适的工具。
每个工具都有以下配置选项:
- 名称
- 来源
- 使用参数进行图模式匹配并返回结果的Cypher查询语句
- 工具或查询的描述
- 每个参数的名称、类型及其描述
工具:
按演员搜索电影:
种类: neo4j-cypher
来源: my-neo4j-movies-instance
查询语句: |
MATCH (m:Movie)<-[:ACTED_IN]-(p:Person)
WHERE p.name = $name AND m.year > $year
RETURN m.title, m.year
ORDER BY m.year DESC
LIMIT 10
描述: |
使用此工具获取特定演员及其给定的最早发布年份的电影列表。提供完整的演员姓名,例如 "Tom Hanks" 和年份,例如 1993,返回电影标题和发行年份的列表。
不要用此工具搜索电影标题。不要猜测演员姓名,不要猜测年份。演员姓名应是完整的,包括名和姓,中间用空格分隔。
例如,如果提供 "Hanks, Tom",演员姓名应为 "Tom Hanks"。
如果工具返回多个结果,请选择最新的电影。
示例:
{{
"name": "Meg Ryan",
"year": 1993
}}
示例:
{{
"name": "Clint Eastwood",
"year": 2000
}}
参数:
- 参数名称: 名称
类型: 字符串
描述: 完整的演员姓名,例如 "firstname lastname"
- 参数名称: 年份
类型: 整数
描述: 从1900到当前年的四位数
投资研究员
这是展示一个代理型LangChain应用程序的示例,该应用程序使用工具结合全文搜索和图搜索功能,采用GraphRAG模式。
这个例子展示了一个投资研究代理人,可以用来查找有关公司、其投资者、竞争对手、合作公司和行业的最新新闻。它由Diffbot知识图谱中的数据驱动,并已导入Neo4j中。
示例代码可以在这个仓库中找到:this repository。
GitHub - jexp/toolbox-companies: Google AI 工具箱投资研究工具项目。通过在 github.com 创建账户并为 jexp/toolbox-companies 的开发做出贡献。 公司和文章:来自 Diffbot 的资源该数据集是一个关于公司、相关行业、在这些公司工作或投资的人士以及有关这些公司的报道的图表。
文章图、行业图和公司图
新闻文章被分割成块,并且这些块也被存放在图中。
数据库可供公众使用,并提供了一个只读用户账号。你可以在https://demo.neo4jlabs.com:7473/browser/浏览数据。
- URI:
neo4j+s://demo.neo4jlabs.com
- 用户名:
companies
- 密码:
companies
- 公司名称:
companies
我们使用代理式LangChain集成与Vertex AI,这使我们能够自动将我们在Toolbox里注册的工具传递给LLM进行工具调用。我们将使用混合搜索技术,以及父子检索器和GraphRAG(用于提取相关上下文)。
在我们系统中,我们提供有利用全文索引的一些工具,以及用于图检索的查询,这些查询会获取以下额外的数据。
Parent
文章的Chunk
(聚合一个文章的所有Chunk
)- 提到的组织(Organization)
- 与组织相关的行业类别(IndustryCategory)
- 与组织相关的人员及其角色(例如,投资者、主席、首席执行官)
以下这些是工具箱中的工具,具有特定查询功能,供代理使用。
每个工具,从代理获取参数,执行图上的查询,并返回结构化的结果:
industries
— 行业类别名称列表companies_in_industry
— 按行业列出特定行业的公司(ID,名称,简介)companies
— 按全文搜索搜索公司列表(ID,名称,简介)articles_in_month
— 按给定日期查询一个月时间段内的文章列表(ID,作者,标题,日期,情感)article
— 通过文章ID查询单篇文章详情(ID,作者,标题,日期,情感,来源网站,简介,内容)companies_in_articles
— 通过文章ID列表查询在文章中提及的公司(ID,名称,简介)people_at_company
— 通过公司ID查询与公司相关的人员(姓名,职位)
在我们的 tools.yaml
文件的 sources 部分,我们配置公共 Neo4j 公司图数据库的连接作为源。
数据源:
公司图谱:
类型: "neo4j"
URI: "neo4j+s://demo.neo4jlabs.com"
用户名: "companies"
密码: "companies"
数据库: "companies"
Neo4j Cypher 设置教程
现在我们就能用工具箱(Toolbox)配置我们要测试的前两个工具;其他工具的配置和描述方式类似,如下:
industries
— 行业名列表companies_in_industry
— 某行业的公司列表(ID,名称,简介)
工具:
行业:
种类: neo4j-cypher
源: companies-graph
查询语句: |
MATCH (i:IndustryCategory)
RETURN i.name AS 行业名称
描述: 行业名称列表
行业内的公司:
种类: neo4j-cypher
源: companies-graph
查询语句: |
MATCH (:IndustryCategory {name:$industry})<-[:HAS_CATEGORY]-(c)
WHERE NOT EXISTS { (c)<-[:HAS_SUBSIDARY]-() }
RETURN c.id AS company_id, c.name AS 名称, c.summary AS 简介
描述: 给定行业中的公司(company_id, 名称, 简介)
参数:
- 名称: industry
类型: 字符串
描述: 用于筛选公司的行业名称参数
启动 Toolbox 并加载我们的 YAML 配置文件后,它通过 API 提供工具定义信息,这些定义可以通过 ToolboxClient 进行获取,并传递给 LangChain 工具。
# one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
export OS="linux/amd64"
curl -O https://storage.googleapis.com/genai-toolbox/v0.1.0/$OS/toolbox
chmod +x toolbox
./toolbox --tools_file tools.yaml
2025-02-13T13:50:22.052198+01:00 INFO "初始化了 1 个来源。"
2025-02-13T13:50:22.052967+01:00 INFO "初始化了 0 个认证来源。"
2025-02-13T13:50:22.053095+01:00 INFO "初始化了 2 个工具组件。"
2025-02-13T13:50:22.053146+01:00 INFO "初始化了 1 套工具。"
2025-02-13T13:50:22.053742+01:00 INFO "服务已启动并准备好!"
我们现在可以使用LangChain和Gemini 2.0 Flash模型,并将我们工具的定义输入到模型中来进行一次快速测试。可以参考Toolbox文档中的快速入门示例。
import asyncio
import os
from langgraph.prebuilt import create_react_agent
from langchain_google_vertexai import ChatVertexAI
from langgraph.checkpoint.memory import MemorySaver
from toolbox_langchain import ToolboxClient
prompt = """
你是一个乐于助人的投资研究助手,可以使用提供的工具来搜索公司、人员、行业以及2023年的新闻文章。
不需要向用户请求确认。
用户:
"""
queries = [
"哪些行业涉及计算机制造?",
"请列出计算机制造行业中五家公司的简要描述。",
]
def main():
model = ChatVertexAI(model_name="gemini-1.5-pro")
client = ToolboxClient("http://127.0.0.1:5000")
tools = client.load_toolset()
agent = create_react_agent(model, tools, checkpointer=MemorySaver())
config = {"configurable": {"线程ID": "thread-1"}}
for query in queries:
inputs = {"messages": [("用户", prompt + query)]}
response = agent.invoke(inputs, stream_mode="values", config=config)
print(response["messages"][-1].content)
main()
我们得到的输出如下:
以下是与计算机制造相关的行业:
计算机硬件公司、电子产品制造商、计算机存储设备公司。
以下是五家计算机硬件行业的公司及其描述:
- 微软埃及:微软在埃及的分公司
- Apigee:位于美国加利福尼亚州圣何塞的软件公司,隶属于谷歌
- Microsemi:通信公司
- Intermec:美国电子产品制造商
- Elitegroup计算机系统:暂无更多信息
现在这可以用了,我们可以添加其他工具了。
companies
— 按全文搜索获取公司列表(ID,名称,简介)articles_in_month
— 从给定日期(yyyy-mm-dd)开始的一个月内的文章列表(ID,作者,标题,日期,情感分析)article
— 通过文章ID获取单篇文章的详细信息(ID,作者,标题,日期,情感分析,网站,简介,内容)companies_in_articles
— 通过文章ID列表获取提到的公司(ID,名称,简介)people_at_company
— 通过公司ID获取与该公司相关的人员(名字,职位)
请注意,处理数组参数时,您需要提供一个“items”子组来指定子类型名称和描述。
工具集:
公司:
种类: neo4j-cypher
源: companies-graph
语句: |
CALL db.index.fulltext.queryNodes('entity', $search, {limit: 100})
YIELD node as c, score WHERE c:Organization
AND NOT EXISTS { (c)<-[:HAS_SUBSIDARY]-() }
RETURN c.id as company_id, c.name as name, c.summary as summary
描述: 按全文查询的公司列表(company_id, name, summary)
参数:
- 名称: search
类型: string
描述: 要查询的公司的名称的一部分
当月文章:
种类: neo4j-cypher
源: companies-graph
语句: |
MATCH (a:Article)
WHERE date($date) <= date(a.date) < date($date) + duration('P1M')
RETURN a.id as article_id, a.author as author, a.title as title, toString(a.date) as date, a.sentiment as sentiment
LIMIT 25
描述: 从给定日期开始的一个月内文章列表(article_id, author, title, date, sentiment)
参数:
- 名称: date
类型: string
描述: yyyy-mm-dd 格式的开始日期
文章:
种类: neo4j-cypher
源: companies-graph
语句: |
MATCH (a:Article)-[:HAS_CHUNK]->(c:Chunk)
WHERE a.id = $article_id
WITH a, c ORDER BY id(c) ASC
WITH a, collect(c.text) as contents
RETURN a.id as article_id, a.author as author, a.title as title, toString(a.date) as date,
a.summary as summary, a.siteName as site, a.sentiment as sentiment, apoc.text.join(contents, ' ') as content
描述: 通过文章 ID 获取单篇文章的详细信息(article_id, author, title, date, sentiment, site, summary, content)
参数:
- 名称: article_id
类型: string
描述: 要检索的文章 ID
文章中的公司:
种类: neo4j-cypher
源: companies-graph
语句: |
MATCH (a:Article)-[:MENTIONS]->(c)
WHERE a.id = $article_id AND not exists { (c)<-[:HAS_SUBSIDARY]-() }
RETURN c.id as company_id, c.name as name, c.summary as summary
描述: 根据文章 ID 获取文章中提到的公司(company_id, name, summary)
参数:
- 名称: article_id
类型: string
描述: 要查找公司被提及的文章 ID
与公司相关的人员:
种类: neo4j-cypher
源: companies-graph
语句: |
MATCH (c:Organization)-[role]-(p:Person) WHERE c.id = $company_id
RETURN replace(type(role),"HAS_","") as role, p.name as person_name, c.id as company_id, c.name as company_name
描述: 通过公司 ID 获取与公司相关的人员列表(person_name, role, company_id, company_name)
参数:
- 名称: company_id
类型: string
描述: 要查找相关公司的公司 ID
使用代理功能 — 聊天
我被下面的对话以及大语言模型能够准确使用工具来收集所有回答问题所需的全部信息的能力深深打动了。
LLM不仅能够多次使用端点,还可以参考对话历史中的信息来确保相关上下文被考虑到。
它可以修复拼写错误或不正确的参数,并将其参数内容更正为正确的,重新格式化日期以符合所需的格式,还可以执行更多任务。API 函数可以被多次调用,并且如有必要,它们会级联调用。例如,为特定上下文检索 ID 列表,然后对每个 ID 进行单独调用以检索详细信息。
它还能够解析代词和指示词(例如,“这些”会具体解析为一些公司的ID或文章内容,然后用于进行多次API调用)。
我会针对每个问题发表一些看法,指出其中的难点,以及代理是如何处理这些难点的。大型语言模型代理在此过程中展示了其解决问题的能力。
让我们再看看我们向研究代理提出的更多问题,看看它的表现如何。
- 哪五个行业涉及神经植入技术?
- 列出这些行业中位于加利福尼亚的三家公司及其描述。
- 在这些公司工作的都有谁?
- 2023年1月有哪些公司因为积极的新闻报道而被提及?
- 这些公司的董事会成员都有谁?
回答:
哪些行业会用到神经植入?
我们先来做个简单的任务——调用产业端点接口,但是只返回最多五个结果(该接口本身不支持设置结果数量限制)。对于具体的产业类型则没有具体说明。
根据现有的行业,以下这几个看起来最相关:神经植入体。
- 医疗技术公司
- 医疗健康公司
- 生物技术公司
- 制药公司
- 机器人公司
从这三个行业中各列出一家公司并附上描述,然后过滤出位于加州的公司。
利用先验信息,设定参数,并进行后处理。在这个问题里,我们对行业提供了一般性的参考,并提供了一个额外的位置信息,虽然它不是一个参数,但需要进行后处理。
它利用对话中的先前信息来锁定行业名称,并应用后过滤和限制,仅显示三家位于加州的公司。
行了,我有很多家公司。我会找出位于加州的公司,然后列出五家公司的简介:
- goBalto: (生物技术公司,制药公司) 一家位于美国加州旧金山的软件开发公司
- Quantenna Communications: (半导体公司,电子产品制造商) 一家位于美国加州圣何塞的半导体制造公司,由安森美半导体所有
- 等
这些公司里都有谁在工作?
_在接下来的问题中,我们用“这些”回指提到的那些公司,并要求获取可以在其他端点获得的详细信息。不过,代理需要多次调用people_atcompany端点,因为每次只能处理一个公司的ID。
以下是一些在这些公司工作的人:
- goBalto: 苏杰·贾达瓦(CEO),达伦·海特(董事),清居民正(董事)
- Quantenna Communications: 萨姆·海达里(CEO),德米特里·阿克汉诺夫、埃德温·B·霍珀三世“Ned”、杰克·拉扎尔和马克·斯蒂文斯(董事)
- Finisar: 鲍勃·巴肖(CEO),玛丽·简·雷蒙德(董事)
- …
2023年1月的新闻中提到的哪些公司有正面评价?
_在这里,我们想要做两件事,即从讨论公司转到文章的讨论,而不重置会话。我们提供了一个部分日期,即2023年1月,需要将其转换为yyyy-mm-dd格式,以满足API调用的需求。它需要理解“正面情绪”指的是数值评分,并且我们希望得到最高的评分。对于带有情绪评分的文章列表,模型需要阅读、理解、根据情绪评分从高到低排序,选出得分最高的文章,并输出这些文章的标题以及提到的公司名称。这并不是同一个接口,因此需要单独查询每篇文章的companies_inarticle工具。
以下是2023年1月积极情绪评分的文章以及提及的公司:
- BackBox 发布定制产品更新 (0.886): 我没有工具来识别文中提到的公司。
- Mozilla 的 Pocket 应用程序刚刚进行了重大更新 (0.838, 0.83): Mozilla
- Onapsis 和 Wipro 帮助企业保护他们的 SAP 应用程序 (0.612): Onapsis, Wipro, SAP
- Onapsis 宣布与 Wipro 合作,以消除数字转型中的安全障碍 (0.804, 0.735): Onapsis, Wipro
- 休斯顿地铁与 Lone Star 学院为 Lone Star College-Houston North Fallbrook 校区推出新试点服务 (0.816): Lone Star College
- 等等
那儿的董事会成员都有谁?
在这里,我们用“那里”来指代前面提到的公司,并且只是模糊地指定了进行API调用所需的角色。代理人需要多次调用people_at_company端点,使用之前在会话历史中检索到的公司ID,然后将信息汇总和筛选后放入响应中。
我没有为所有的公司找到董事会成员的信息。以下是找到的信息如下:
无法为所有公司找到董事会成员的信息。
- Wipro:
Azim Premji(董事)
Rishad Premji(董事) - SAP:
Hasso Plattner(董事)
Jennifer Morgan(董事) - Google:
Larry Page(董事)
Sergey Brin(董事)
工具箱服务器和LangGraph应用的部署不在本文的讨论范围内。请参考以下链接:GenAI Toolbox部署指南和LangChain在Cloud Run上的部署教程。
来个总结我们展示了如何构建代理型应用程序,使其能够轻松地与Google Gen AI工具箱中的数据库工具集成。虽然我们以Neo4j中的知识图谱的GraphRAG用例为例进行了说明,但您可以在同一个代理应用程序中结合不同的数据源和数据库类型。由于工具箱可以与不同的代理框架集成,您可以在多个用例中重用工具。作为开源项目,为工具箱贡献非常直接。我们希望您能够利用反馈和讨论机制来添加自己的数据源,并提供反馈和改进意见。
您可以在什么是知识图谱?了解更多信息。同时,别忘了查看GraphRAG 宣言:为生成AI添加知识。
共同學(xué)習(xí),寫(xiě)下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章
100積分直接送
付費(fèi)專(zhuān)欄免費(fèi)學(xué)
大額優(yōu)惠券免費(fèi)領(lǐng)