LLM-文本分块(langchain)与向量化(阿里云DashVector)存储,嵌入LLM实践

文章目录

  • 前言
  • 向量、令牌、嵌入
  • 分块
    • 按字符拆分
    • 按字符递归拆分
    • 按token拆分
  • 向量化
  • 使用 TextEmbedding 实现语义搜索
    • 数据准备
    • 通过 DashScope 生成 Embedding 向量
    • 通过 DashVector 构建检索:向量入库
    • 语义检索:向量查询
    • 完整代码
  • 总结

前言

Transformer 架构已经成为当今深度学习的趋势,大家一定也经常听到 token, 向量化嵌入等名词概念,可以说这三个是 Transformer 架构实现自注意力的关键,限于本文主题,下边会简单介绍下这三个概念,以及之间的关系,但不会详细多说,网上已经有很多比较好的文章进行了说明总结。

现在有很多开源的库可以对文本进行分块,比如说 langchaintext_splitter包,以及许多优秀的开源向量数据库,比如说Chroma,Milvus,Weaviate等,虽然我们可以使用这些开源的工具进行分块和向量化存储,但是我一直认为上云,使用云服务才是大势所趋(将这些中间件服务交给云服务商可以大大减少公司的运维费用)。

本文首先会对阿里云的向量检索服务DashVector进行简单使用说明,然后会结合灵积模型服务上的Embedding API,来从0到1构建基于文本索引的构建+向量检索基础上的语义搜索能力。具体来说,我们将基于QQ 浏览器搜索标题语料库(QBQTC:QQ Browser Query Title Corpus)进行实时的文本语义搜索,查询最相似的相关标题。

前提条件:

  • 开通灵积模型服务,并获得 API-KEY:开通DashScope并创建API-KEY。
  • 开通DashVector向量检索服务,并获得 API-KEYAPI-KEY管理。
  • 已创建向量检索服务Cluster:创建Cluster。
  • 已获得向量检索服务API-KEY:API-KEY管理。
  • 已安装最新版SDK:安装DashVector SDK。

向量、令牌、嵌入

向量 (Vectors)

  • 向量在LLM中扮演了基石的角色,通过将文本数据转换成高维向量空间中的表示,使机器能够理解语言。这种转换后的向量称为“语义向量嵌入”,能够编码和体现文本的实际语义信息。
  • 与传统离散符号表示相比,语义向量嵌入能够自动捕捉并编码单词间的同义关系、语法关联及上下文语义信息,使得语义相似的词语在向量空间中彼此接近。
  • 这种连续的向量表示简化了数据结构的复杂度,为神经网络模型提供了紧凑且信息丰富的内部数据形式,显著提升了模型的学习和表现能力。

令牌 (Tokens)

  • 令牌是文本数据在LLM内部的表示形式,可以是单词、子词或字符,具体取决于令牌化策略。
  • 令牌化是将原始文本转换为模型可解释的离散符号序列的过程,这一过程对模型的输入表示和计算效率产生直接影响。
  • 合理的令牌化策略不仅能减少词汇表大小,解决未知词问题,还能为模型提供更好的语义信号,提高其泛化能力。不同LLM会采用不同的令牌化方案以最大化发挥模型潜力。

嵌入 (Embeddings)

  • 嵌入是赋予令牌以语义语境的关键环节,代表了文本的意义和上下文信息。它是一种融入了语境的令牌表征,由嵌入模型生成,以向量形式存在,捕捉了令牌之间的语义关联和上下文依赖。
  • 嵌入不仅编码了令牌的身份,更重要的是捕捉了令牌间的关系,使得LLM能够理解语境、微妙语义和词语/短语的细微差异。高质量的嵌入使得LLM在各类自然语言处理任务中展现出人类甚至超人类水平的能力。

三者关系

  • 令牌、向量和嵌入在LLM的处理流程中紧密相关又各具特色。令牌是语言的最小单元,每个令牌在模型底层表现为向量,便于机器计算。
  • 向量为令牌提供了数学框架,但仅凭向量难以准确反映语义信息,此时嵌入技术显得尤为重要。
  • 嵌入是经过专门训练的向量表示,能够体现词语/短语间的相似性、类比关系等丰富语义,为LLM提供更细腻的语义理解基础。
  • 令牌化将文本转换为离散标记,而嵌入则将这些标记映射到语义向量空间,赋予上下文语境,实现了从离散语言符号到连续语义空间的转变。

分块

pip install langchain

按字符拆分

# 读取需要拆分的文本内容
with open("../../splitters_test.txt") as f:state_of_the_union = f.read()from langchain_text_splitters import CharacterTextSplittertext_splitter = CharacterTextSplitter(separator="\n\n",chunk_size=1000,chunk_overlap=200,length_function=len,is_separator_regex=False,
)texts = text_splitter.create_documents([state_of_the_union])
print(texts[0])
  • separators - 分隔符字符串数组
  • chunk_size - 每个文档的字符数量限制
  • chunk_overlap - 两份文档重叠区域的长度
  • length_function - 长度计算函数
  • is_separator_regex - 如果为真:应当被解释为正则表达式,因此不需要转义。如果为假:应当被当作普通字符串分隔符,并转义任何特殊字符。

将元数据与文档一起传递:

meta_datas = [{"document": 1}, {"document": 2}]
documents = text_splitter.create_documents([state_of_the_union, state_of_the_union], metadatas=meta_datas
)
print(len(documents))
print(documents[0])
print(documents[3])

按字符递归拆分

对于通用文本,建议使用此文本拆分器。它由字符列表参数化。它试图按顺序分割它们,直到这些块足够小。默认列表是 [“\n\n”, “\n”, " ", “”] 。这样做的效果是尽量将所有段落(然后是句子,然后是单词)保持在一起,因为这些段落一般看起来是语义最相关的文本片段。

from langchain_text_splitters import RecursiveCharacterTextSplitter# This is a long document we can split up.
with open("./demo_static/splitters_test.txt") as f:state_of_the_union = f.read()text_splitter = RecursiveCharacterTextSplitter(# Set a really small chunk size, just to show.chunk_size=100,chunk_overlap=20,length_function=len,is_separator_regex=False,
)texts = text_splitter.create_documents([state_of_the_union])
print(texts[0])
print(texts[1])

从没有单词边界的语言中拆分文本

一些书写系统没有单词边界,例如 中文 、日文和泰文。使用默认分隔符列表拆分文本[“\n\n”, “\n”, " ", “”]会导致单词在单词块之间拆分。

text_splitter = RecursiveCharacterTextSplitter(separators=["\n\n","\n"," ",".",",","\u200B",  # Zero-width space"\uff0c",  # Fullwidth comma"\u3001",  # Ideographic comma"\uff0e",  # Fullwidth full stop"\u3002",  # Ideographic full stop"",],# Existing args
)

按token拆分

使用 tiktoken

pip install --upgrade --quiet langchain-text-splitters tiktoken

from langchain_text_splitters import CharacterTextSplitter
# This is a long document we can split up.
with open("./demo_static/splitters_test.txt") as f:state_of_the_union = f.read()text_splitter = CharacterTextSplitter.from_tiktoken_encoder(model_name="gpt-4", chunk_size=100, chunk_overlap=0
)
texts = text_splitter.split_text(state_of_the_union)

使用 spacy
pip install --upgrade --quiet spacy

from langchain_text_splitters import SpacyTextSplitter
# This is a long document we can split up.
with open("./demo_static/splitters_test.txt") as f:state_of_the_union = f.read()text_splitter = SpacyTextSplitter(chunk_size=1000)texts = text_splitter.split_text(state_of_the_union)
print(texts[0])

使用 SentenceTransformers

from langchain_text_splitters import SentenceTransformersTokenTextSplittersplitter = SentenceTransformersTokenTextSplitter(chunk_overlap=0)
text = "Lorem "count_start_and_stop_tokens = 2
text_token_count = splitter.count_tokens(text=text) - count_start_and_stop_tokens
print(text_token_count)token_multiplier = splitter.maximum_tokens_per_chunk // text_token_count + 1# `text_to_split` does not fit in a single chunk
text_to_split = text * token_multiplierprint(f"tokens in text to split: {splitter.count_tokens(text=text_to_split)}")text_chunks = splitter.split_text(text=text_to_split)print(text_chunks[1])

使用 NLTK
pip install nltk

from langchain_text_splitters import NLTKTextSplitter# This is a long document we can split up.
with open("./demo_static/splitters_test.txt") as f:state_of_the_union = f.read()text_splitter = NLTKTextSplitter(chunk_size=1000)texts = text_splitter.split_text(state_of_the_union)
print(texts[0])

向量化

这里使用阿里云的 DashVector云服务进行向量化操作,包括存储与检索,首先需要确定我们已经开通了上边的前提条件。(现在可以免费开通试用一个月),向量检索服务官方文档。

pip install dashvector

创建 Client
YOUR_API_KEY 和 YOUR_CLUSTER_ENDPOINT 可以在向量检索服务控制台查看 。

import dashvectorclient = dashvector.Client(api_key='YOUR_API_KEY',endpoint='YOUR_CLUSTER_ENDPOINT'
)
assert client

创建Collection
创建一个名称为quickstart,向量维度为4的collection。

client.create(name='quickstart', dimension=4)collection = client.get('quickstart')
assert collection

插入Doc

from dashvector import Doc# 通过dashvector.Doc对象,插入单条数据
collection.insert(Doc(id='1', vector=[0.1, 0.2, 0.3, 0.4]))# 通过dashvector.Doc对象,批量插入2条数据
collection.insert([Doc(id='2', vector=[0.2, 0.3, 0.4, 0.5], fields={'age': 20, 'name': 'zhangsan'}),Doc(id='3', vector=[0.3, 0.4, 0.5, 0.6], fields={'anykey': 'anyvalue'})    ]
)
  • 这里的fields是为了增加检索准确性,可以在检索的时候加上一些过滤条件等,具体的可以参考文档。
  • 插入成功后可以在向量检索服务控制台看到我们插入的向量数据:
    image.png

相似性检索

rets = collection.query([0.1, 0.2, 0.3, 0.4], topk=2)print(rets)# 输出如下
{"code": 0, "message": "Success", "requests_id": "24731ff6-d3b4-475a-9309-d66428b684bc", "output": [{"id": "1", "fields": {}, "score": 0.0}, {"id": "2", "fields": {"age": 20, "name": "zhangsan"}, "score": 0.0062}]}

删除Doc

# 删除1条数据
collection.delete(ids=['1'])

查看Collection统计信息

stats = collection.stats()print(stats)

删除Collection

client.delete('quickstart')

上边都是对简单的 CRUD 操作,还有一些比较高级的使用,比如说使用 Field 字段过滤,增加检索准确性,使用 分区 Partition,向量动态量化等,官方文档都写的很清晰,这里只起一个抛砖引玉的作用,如果要在生产环境中使用,还是得全面了解下官方文档。

使用 TextEmbedding 实现语义搜索

接下来我们利用 DashVector 和 Embedding API 构建一个实时的索引服务和查询。
具体的架构图如下:
image.png
环境安装
pip3 install dashvector dashscope

数据准备

git clone https://github.com/CLUEbenchmark/QBQTC.git
wc -l QBQTC/dataset/train.json

数据集中的训练集(train.json)其格式为 json:

{"id": 0, "query": "小孩咳嗽感冒", "title": "小孩感冒过后久咳嗽该吃什么药育儿问答宝宝树", "label": "1"
}

我们将从这个数据集中提取title,方便后续进行embedding并构建检索服务。

import jsondef prepare_data(path, size):with open(path, 'r', encoding='utf-8') as f:batch_docs = []for line in f:batch_docs.append(json.loads(line.strip()))if len(batch_docs) == size:yield batch_docs[:]batch_docs.clear()if batch_docs:yield batch_docs

通过 DashScope 生成 Embedding 向量

import dashscope
from dashscope import TextEmbedding# 需要使用自己的api-key替换示例中的 your-dashscope-api-key 
dashscope.api_key='{your-dashscope-api-key}'def generate_embeddings(text):rsp = TextEmbedding.call(model=TextEmbedding.Models.text_embedding_v1,input=text)embeddings = [record['embedding'] for record in rsp.output['embeddings']]return embeddings if isinstance(text, list) else embeddings[0]# 查看下embedding向量的维数,后面使用 DashVector 检索服务时会用到,目前是1536
print(len(generate_embeddings('hello')))
  • 需要使用自己的api-key替换示例中的 your-dashscope-api-key

通过 DashVector 构建检索:向量入库

DashVector 向量检索服务上的数据以集合(Collection)为单位存储,写入向量之前,我们首先需要先创建一个集合来管理数据集。创建集合的时候,需要指定向量维度,这里的每一个输入文本经过DashScope上的text_embedding_v1模型产生的向量,维度统一均为1536。

DashVector 除了提供向量检索服务外,还提供倒排过滤功能 和 scheme free 功能。所以我们为了演示方便,可以写入数据时,可以将title内容写入 DashVector 以便召回。写入数据还需要指定 id,我们可以直接使用 QBQTC 中id。

from dashvector import Client, Doc# 初始化 DashVector client
client = Client(api_key='{your-dashvector-api-key}',endpoint='{your-dashvector-cluster-endpoint}'
)# 指定集合名称和向量维度
rsp = client.create('sample', 1536)
assert rspcollection = client.get('sample')
assert collectionbatch_size = 10
for docs in list(prepare_data('QBQTC/dataset/train.json', batch_size)):# 批量 embeddingembeddings = generate_embeddings([doc['title'] for doc in docs])# 批量写入数据rsp = collection.insert([Doc(id=str(doc['id']), vector=embedding, fields={"title": doc['title']}) for doc, embedding in zip(docs, embeddings)])assert rsp

语义检索:向量查询

在把QBQTC训练数据集里的title内容都写到DashVector服务上的集合里后,就可以进行快速的向量检索,实现“语义搜索”的能力。继续上面代码的例子,假如我们要搜索有多少和’应届生 招聘’相关的title内容,可以通过在DashVector上去查询’应届生 招聘’,即可迅速获取与该查询语义相近的内容,以及对应内容与输入之间的相似指数。

# 基于向量检索的语义搜索
rsp = collection.query(generate_embeddings('应届生 招聘'), output_fields=['title'])for doc in rsp.output:print(f"id: {doc.id}, title: {doc.fields['title']}, score: {doc.score}")

完整代码

import jsonimport dashscope
from dashscope import TextEmbedding
from dashvector import Client, Docdef prepare_data(path, size):with open(path, 'r', encoding='utf-8') as f:batch_docs = []for line in f:batch_docs.append(json.loads(line.strip()))if len(batch_docs) == size:yield batch_docs[:]batch_docs.clear()if batch_docs:yield batch_docsdef init_vector_collections(api_key, endpoint):client = Client(api_key=api_key,endpoint=endpoint)# 指定集合名称和向量维度client.create('sample', 1536)collection = client.get('sample')return collectiondef generate_embeddings(text):rsp = TextEmbedding.call(model=TextEmbedding.Models.text_embedding_v1,input=text)embeddings = [record['embedding'] for record in rsp.output['embeddings']]return embeddings if isinstance(text, list) else embeddings[0]# 查看下embedding向量的维数,后面使用 DashVector 检索服务时会用到,目前是1536
# print(len(generate_embeddings('hello')))def write_embeddings(path, collection):batch_size = 10for docs in list(prepare_data(path, batch_size)):# 批量 embeddingembeddings = generate_embeddings([doc['title'] for doc in docs])# 批量写入数据rsp = collection.insert([Doc(id=str(doc['id']), vector=embedding, fields={"title": doc['title']})for doc, embedding in zip(docs, embeddings)])assert rspdef query(query_str, output_field, collection):# 基于向量检索的语义搜索rsp = collection.query(generate_embeddings(query_str), output_fields=[output_field])for doc in rsp.output:print(f"id: {doc.id}, title: {doc.fields['title']}, score: {doc.score}")if __name__ == '__main__':# 下边的 api key 和 endpoint 需要替换为自己的dashscope.api_key = ''vector_api_key = ''vector_endpoint = ''path = 'train.json'collection = init_vector_collections(vector_api_key, vector_endpoint)# write_embeddings(path, collection)query('应届生 招聘', 'title', collection)

查询结果如下:

id: 301, title: 2015四大之路给自己最完美的交代-普华永道pwc2021校园招聘-应届生求职招聘论坛, score: 0.5732
id: 285, title: 关于实习生考研请假的实施意见试行-河南省中医院, score: 0.5834
id: 427, title: 2013年高考就业率高的专业建筑学专业高考热门专业专业解读中国教育在线, score: 0.6353
id: 295, title: 太原理工大学研究生院, score: 0.6492
id: 134, title: 昆山人才网昆山人力资源网-昆山人才网昆山政府旗下唯一官方招聘网昆山人才市场昆山人力资源市场主办, score: 0.6622
id: 202, title: 国家公务员报考条件-搜狗百科, score: 0.6672
id: 435, title: 西南财经大学继续网络教育学院门户网站, score: 0.6685
id: 52, title: 20141月教师要涨工资了峡山区吧百度贴吧, score: 0.6891
id: 193, title: directsalesjobsinhongkong-sep2020jobsdb, score: 0.7003
id: 280, title: 机场副总经理竞聘报告副总经理竞聘述职竞聘稿副总经理兼总经济师竞聘-fanwenqcn, score: 0.7054

总结

如何将数据分块,然后向量化嵌入向量数据库中,是 LLM 能够成功预测下一个 token 的关键,本文简单介绍了阿里云向量数据库 DashVector 的使用,并且使用一个具体的案例,将整个流程给串起来,关于 DashVector 还有很多高级功能这里并没有使用,读者可以 自行探索使用以下。

后续我会结合阿里云的通义千问大模型和 DashVector 打造一个专业的知识库,以及如何使用多模态嵌入和大模型交互的场景实战。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/43995.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[C++][ProtoBuf][Proto3语法][三]详细讲解

目录 1.默认值2.更新消息1.更新规则2.保留字段reserved 3.未知字段1.是什么?2.未知字段从哪获取 4.前后兼容性5.选项option1.选项分类2.常用选项列举3.设置自定义选项 1.默认值 反序列化消息时,如果被反序列化的⼆进制序列中不包含某个字段,…

信息系统项目管理师【一】英文选择题词汇大全(1)

一、计算机相关词汇 数据挖掘 Data Mining分布式计算 Distributed Computing云计算 Cloud Computing物联网 IOT Internet of Things大数据 Big Data人工智能 artificial intelligence互联网 Internet plus区块链 Blockchain5G 5th-Generation感知层 sensing layer机器学习 mac…

基于Spring Boot的旅游信息推荐信息系统设计与实现(源码+lw+部署+讲解)

技术指标 开发语言:Java 框架:Spring BootJSP JDK版本:JDK1.8 数据库:MySQL5.7 数据库工具:Navicat16 开发软件:IDEA Maven包:Maven3.6.3 浏览器:IE浏览器 功能描述 旅游信…

Hadoop-19 Flume Agent批量采集数据到HDFS集群 监听Hive的日志 操作则把记录写入到HDFS 方便后续分析

章节内容 上一节我们完成了内容: Flume 启动测试Flume Conf编写Flume 测试发送和接收数据 背景介绍 这里是三台公网云服务器,每台 2C4G,搭建一个Hadoop的学习环境,供我学习。 之前已经在 VM 虚拟机上搭建过一次,但…

深入探索大语言模型

深入探索大语言模型 引言 大语言模型(LLM)是现代人工智能领域中最为重要的突破之一。这些模型在自然语言处理(NLP)任务中展示了惊人的能力,从文本生成到问答系统,无所不包。本文将从多个角度全面介绍大语…

AGE agtype 简介

AGE 使用一种名为 agtype 的自定义数据类型,这是 AGE 返回的唯一数据类型。agtype 是 Json 的超集,也是 JsonB 的自定义实现。 简单数据类型 Null 在Cypher中,null用于表示缺失或未定义的值。概念上,null表示“缺失的未知值”&…

路径规划 | 基于蚁群算法的三维无人机航迹规划(Matlab)

目录 效果一览基本介绍程序设计参考文献 效果一览 基本介绍 基于蚁群算法的三维无人机航迹规划(Matlab)。 蚁群算法(Ant Colony Optimization,ACO)是一种模拟蚂蚁觅食行为的启发式算法。该算法通过模拟蚂蚁在寻找食物时…

DONT_TOUCH

DONT_TOUCH DONT_TOUCH指示工具不优化用户层次结构、实例化组件或 信号,以便优化不会跨模块边界发生,或者消除 对象虽然这可以帮助进行布图规划、分析和调试,但它可以抑制 优化,导致更大、更慢的设计。 重要提示:Xilin…

数据赋能(143)——开发:数据拆分——概述、关注焦点

概述 数据拆分是指将一个大型的数据集合按照特定的规则或条件划分成多个较小的、更易于管理的数据子集的过程。 数据拆分操作属于数据整理过程。 这些子集可能基于数据的某个特征、时间范围、地理位置或其他属性进行划分,以便于单独分析、处理或存储。 数据拆分…

【安全设备】Web应用防火墙

一、什么是Web应用防火墙 Web应用程序防火墙(Web Application Firewall)的缩写是WAF,用于保护Web应用程序免受各种恶意攻击和漏洞利用。WAF通过监控和过滤进出Web应用程序的HTTP/HTTPS流量来工作。它位于Web应用程序和用户之间,分…

【总线】AXI第九课时:介绍AXI响应信号 (Response Signaling):RRESP和 BRESP

大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计感兴趣,那你绝对不能错过我们今天的主角——AXI4总线。作为ARM公司AMBA总线家族中的佼佼者,AXI4以其高性能和高度可扩展性,成为了现代电子系统中不可或缺的通信桥梁…

spring监听事件

1、spring-监听事件基本原理 Spring的事件监听机制和发布订阅机制是很相似的:发布了一个事件后,监听该类型事件的所有监听器会触发相应的处理逻辑 2、Spring 监听事件相关规范 在Spring中,事件监听机制主要涉及到了一下几个关键的规范&#x…

AI学习指南机器学习篇-层次聚类距离度量方法

AI学习指南机器学习篇-层次聚类距离度量方法 引言 在机器学习领域中,层次聚类是一种有用且常见的聚类方法。它通过构建一个层次化的聚类树,将数据集中的样本逐步分组,从而实现聚类任务。在层次聚类过程中,距离度量方法是决定样本…

STM32F103RB多通道ADC转换功能实现(DMA)

目录 概述 1 硬件 1.1 硬件实物介绍 1.2 nucleo-f103rb 1.3 软件版本 2 软件实现 2.1 STM32Cube配置参数 2.2 项目代码 3 功能代码实现 3.1 ADC功能函数 3.2 函数调用 4 测试 4.1 DMA配置data width:byte 4.2 DMA配置data width:Half wor…

java如何实现一个死锁 ?

死锁(Deadlock)是指在并发系统中,两个或多个线程(或进程)因争夺资源而互相等待,导致它们都无法继续执行的一种状态。 一、简易代码 public class DeadlockExample {private static final Object lock1 = new Object();private

如何在 ASP.NET MVC 项目中使用身份验证器应用程序实现多因素身份验证?

介绍 增强安全性对于任何应用程序都至关重要,而多因素身份验证 (MFA) 是实现此目标的有效方法。在本文中,我们将介绍在 ASP.NET MVC 项目中使用身份验证器应用程序集成 MFA 的过程。无论您是从头开始还是将 MFA 添加到现有项目,本指南都将提…

Qt中用label控件显示图像时,无法跟上图像处理速度一种解决方法。

问题描述: 为了不阻塞主线程,将图像推理部分放在新的子线程,采用信号槽传递处理结果和显示图像。 但是主线程的更新仍跟不上子线程处理速度,使得图像显示出现截断,噪声等情况。 一个可行的解决方法是在子线程中添加…

Python面试题:如何在 Python 中发送 HTTP 请求?

在 Python 中发送 HTTP 请求可以使用多个库,其中最常用的是 requests 库。这个库非常直观和易于使用,支持多种 HTTP 方法,如 GET、POST、PUT、DELETE 等。以下是如何使用 requests 库发送 HTTP 请求的一些示例: 安装 requests 库…

生物素标记降钙素Biotin-α-CGRP, rat 中间体

生物素标记降钙素Biotin-α-CGRP, rat 中间体是一种特定的生物化学试剂,主要用于科学研究领域。以下是对该产品的详细介绍: 一、基本信息 产品名称:生物素标记降钙素Biotin-α-CGRP, rat 中间体 英文名称:Biotin-α-CGRP, rat 纯度…

Object.defineProperty与Proxy对比【简单易懂】

目录 简介语法对比实践对比Proxy 解决的问题结论 简介 JavaScript 提供了多种方式来定义和修改对象的属性。Object.defineProperty() 方法允许精确控制对象属性的特性,而 Proxy 对象则提供了一种更为强大和灵活的方式来拦截和自定义操作(如属性查找、赋…