《使用 LangChain 进行大模型应用开发》学习笔记(一)

news2024/9/29 19:09:23

前言

本文是 Harrison Chase (LangChain 创建者)和吴恩达(Andrew Ng)的视频课程《LangChain for LLM Application Development》(使用 LangChain 进行大模型应用开发)的学习笔记。由于原课程为全英文视频课程,国内访问较慢,同时我整理和替换了部分内容以便于国内学习。阅读本文可快速学习课程内容。

课程介绍

本课程介绍了强大且易于扩展的 LangChain 框架,LangChain 框架是一款用于开发大语言模型(LLM)应用的开源框架,其使用提示词、记忆、链、代理等简化了大语言模型应用的开发工作。由于 LangChain 仍处于快速发展期,部分 API 还不稳定,课程中的部分代码已过时,我使用了目前最新的 v0.2 版本进行讲解,所有代码均可在 v0.2 版本下执行。另外,课程使用的 OpenAI 在国内难以访问,我替换为国内的 Kimi 模型,对于学习没有影响。参考这篇文章来获取 Kimi 的 API 令牌。

课程一共分为三个部分:

  • 第一部分
  • 第二部分(待发布)
  • 第三部分(待发布)

在这里插入图片描述

课程链接

第一部分

LangChain 简介

LangChain 是一个可用于构建 LLM 应用的开源框架,支持 Python 和 Javascript。LangChain 关注组合和模块化,提供了链来组合构建复杂的任务。在核心模块之外,可以通过丰富的第三方扩展,集成 OpenAI 等服务。同时 LangChain 还提供了 LangChain Expression Language (LCEL) 来快速构建组件链。

接下来我们通过一些简单的例子来看看 LangChain 的使用。

模型、提示词和解析器

模型指的是大语言模型及相关支撑服务,提示词指的是创建给模型的输入,而解析器则是反过来,从模型获取输出,并转换为结构化的格式。在进行 LLM 应用开发时,我们总是需要反复将提示词提交给模型,并从它那里获取结果并解析。而 LangChain 则提供了一系列的抽象层来简化这部分的操作。

对话 API

我们可以直接使用大语言模型提供的 API 服务来构建 LLM 应用(课程使用的是 openai,但国内使用不便,这里替换为 Kimi)。首先我们来看一段代码。

import openai

base_url = 'https://api.moonshot.cn/v1'  # 替换为 Kimi 服务地址
api_key = 'sk-......'  # Kimi 的 API 令牌,获取方式见课程介绍
llm_model = 'moonshot-v1-8k'  # 模型名称

def get_completion(prompt, model=llm_model):
    messages = [{"role": "user", "content": prompt}]
    client = openai.OpenAI(
        api_key=api_key,
        base_url=base_url,
    )
    completion = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=0.3,
    )
    return completion.choices[0].message.content

要运行这段代码,需要先安装 openai 依赖。

pip install openai

上面这段代码使用了 openai 库(替换为 Kimi 大模型)远程调用大模型的 API 接口进行提问,第一个参数就是我们的提示词。我们可以这样使用。

res = get_completion("1 + 1 等于几?")
print(res)

然后就会得到类似下面这样的回答。

1 + 1 等于 2。在数学中,这是一个基本的加法运算。当我们将两个相同的数相加时,结果是一个比原来数大1的数。在这个例子中,两个数都是1,所以相加后得到2。

LLM 并不总是返回相同的结果,每次执行可能会略有不同。

我们可以使用变量替换来构建更复杂的提示词。

customer_email = """
Arrr, I be fuming that me blender lid \
flew off and splattered me kitchen walls \
with smoothie! And to make matters worse,\
the warranty don't cover the cost of \
cleaning up me kitchen. I need yer help \
right now, matey!
"""

style = "中文,语气平和"

prompt = f"""请将下面双引号内的文本翻译为指定的样式:{style} \
文本: "{customer_email}"
"""
res = get_completion(prompt)
print(res)

可以获取到类似这样的回答。

哎呀,我真是气坏了,我的搅拌机盖子飞出去了,把我的厨房墙壁弄得到处都是奶昔!更糟糕的是,保修不包括清理厨房的费用。我现在就需要你的帮助,伙计!

现在让我们看看在 LangChain 中怎么使用。

首先需要安装依赖。

pip install langchain langchain-core langchain-openai

然后看看如下代码,和上面第二种对话有类似的结果。

from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

template_string = """请将下面双引号内的文本翻译为指定的样式:{style} \
文本: "{text}"
"""
chat = ChatOpenAI(temperature=0.0, model=llm_model, base_url=base_url, api_key=api_key)
prompt_template = ChatPromptTemplate.from_template(template_string)
# print(prompt_template.messages[0].prompt)
# print(prompt_template.messages[0].prompt.input_variables)
customer_messages = prompt_template.format_messages(
    style=style,
    text=customer_email)
# print(type(customer_messages))
# print(type(customer_messages[0]))
customer_response = chat(customer_messages)
print(customer_response.content)

上面的代码可以看到,LangChain 包装了 openai 的接口,ChatOpenAI 就是我们和模型交互的对象,LangChan 还提供了其他很多模型的整合。而 ChatPromptTemplate 提供了一种声明和注入变量的方式,使用 ChatPromptTemplate 就无需 Python 的变量拼接了。使用 format_messages 生成提示词,并使用模型调用,即可获取结果。

上面的代码还不能体现 LangChain 的优势,下面我们来看看解析器的使用。

输出解析器

假设我们现在有如下一段文字,我们想从中获取提取这个物品的信息,提供 JSON 格式的结果。

customer_review = """\
This leaf blower is pretty amazing.  It has four settings:\
candle blower, gentle breeze, windy city, and tornado. \
It arrived in two days, just in time for my wife's \
anniversary present. \
I think my wife liked it so much she was speechless. \
So far I've been the only one using it, and I've been \
using it every other morning to clear the leaves on our lawn. \
It's slightly more expensive than the other leaf blowers \
out there, but I think it's worth it for the extra features.
"""

我们可以这样编辑提示词。

review_template = """\
对于下面的文本,请提取出其中的信息:

gift: 该物品是作为礼物为他人购买的吗? \
如果是回答 True, 否或者不清楚回答 False。

delivery_days: 该物品需要多少天才能到达? \
如果找不到这个信息则输出 -1。

price_value: 提取任何有关物品价值的句子,\
使用逗号分割的 Python list 样式输出,文本翻译为中文。

使用 JSON 格式化结果,包含以下键:
gift
delivery_days
price_value

文本: {text}
"""
prompt_template = ChatPromptTemplate.from_template(review_template)
print(prompt_template)
messages = prompt_template.format_messages(text=customer_review)
chat = ChatOpenAI(temperature=0.0, model=llm_model, base_url=base_url, api_key=api_key)
response = chat(messages)
print(response.content)

运行后可以获取类似下面的结果。

在这里插入图片描述

如果我们需要在代码中使用这个结果,需要使用字符串解析(如 JSON 字符串解析)等方式,重复工作且容易出错。而 LangChain 提供了结果解析器,可以方便地将结果转换为 Python 字典。

首先引入依赖。

from langchain.output_parsers import ResponseSchema
from langchain.output_parsers import StructuredOutputParser

然后定义返回结构。

gift_schema = ResponseSchema(name="gift",
                             description="该物品是作为礼物为他人购买的吗?如果是回答 True, 否或者不清楚回答 False。")
delivery_days_schema = ResponseSchema(name="delivery_days",
                                      description="该物品需要多少天才能到达?如果找不到这个信息则输出 -1。")
price_value_schema = ResponseSchema(name="price_value",
                                    description="提取任何有关物品价值的句子,使用逗号分割的 Python list 样式输出,文本翻译为中文。")

response_schemas = [gift_schema,
                    delivery_days_schema,
                    price_value_schema]

接着创建解析器。

output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
format_instructions = output_parser.get_format_instructions()
print(format_instructions)

最后调用模型,并使用解析器处理结果。

review_template_2 = """\
对于下面的文本,请提取出其中的信息:

gift: 该物品是作为礼物为他人购买的吗? \
如果是回答 True, 否或者不清楚回答 False。

delivery_days: 该物品需要多少天才能到达? \
如果找不到这个信息则输出 -1。

price_value: 提取任何有关物品价值的句子,\
使用逗号分割的 Python list 样式输出,文本翻译为中文。

文本: {text}

{format_instructions}
"""

prompt = ChatPromptTemplate.from_template(template=review_template_2)
messages = prompt.format_messages(text=customer_review,
                                  format_instructions=format_instructions)
chat = ChatOpenAI(temperature=0.0, model=llm_model, base_url=base_url, api_key=api_key)
response = chat(messages)
print(response.content)
output_dict = output_parser.parse(response.content)
print(output_dict)
print(type(output_dict))

输出结果如下。

在这里插入图片描述
我们就可以获得 Python 字典类型的结果,这样我们就可以在 LLM 应用中获得结构化的结果了。

记忆

记忆(Memory)是一种用于存储数据的工具,由于 LLM 没有长期记忆,使用它在多次调用之间保存状态。

ConversationBufferMemory

我们先使用 ConversationBufferMemory 作为记忆的例子。

from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory

# 使用 Kimi 的 URL 和 API 令牌
llm = ChatOpenAI(temperature=0.0, model=llm_model, base_url=base_url, api_key=api_key)
# 创建 memory
memory = ConversationBufferMemory()
conversation = ConversationChain(
    llm=llm,
    memory=memory,
    verbose=True  # 输出格式化后提交的提示词
)
print(conversation.predict(input="你好,我的名字是 wwtg99"))
print(conversation.predict(input="1 + 1 等于几?"))
# time.sleep(60) # 如果使用免费 Kimi API,有速度限制,需要手动限速
print(conversation.predict(input="我的名字是什么?"))

我们会得到类似下面这样的回答。我们首先告诉 LLM 自己的名字,然后问它一个其他的问题,然后再问它我的名字是什么?LLM 记得之前我告诉它的我的名字。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:

Human: 你好,我的名字是 wwtg99
AI:

> Finished chain.
你好,wwtg99!很高兴认识你。我是你的人工智能助手,可以帮你解答问题和提供信息。请问有什么我可以帮助你的吗?


> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: 你好,我的名字是 wwtg99
AI: 你好,wwtg99!很高兴认识你。我是你的人工智能助手,可以帮你解答问题和提供信息。请问有什么我可以帮助你的吗?
Human: 1 + 1 等于几?
AI:

> Finished chain.
1 + 1 等于 2。这是一个基本的数学加法问题,表示将两个相同的数值相加得到的结果。


> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: 你好,我的名字是 wwtg99
AI: 你好,wwtg99!很高兴认识你。我是你的人工智能助手,可以帮你解答问题和提供信息。请问有什么我可以帮助你的吗?
Human: 1 + 1 等于几?
AI: 1 + 1 等于 2。这是一个基本的数学加法问题,表示将两个相同的数值相加得到的结果。
Human: 我的名字是什么?
AI:

> Finished chain.
你的名字是 wwtg99。在我们之前的对话中,你告诉我的。如果你有其他问题或需要帮助,请随时告诉我。

通过输出完整的提示词,我们可以看到,LangChain 将我们之前的对话整理之后作为对话上下文传递给了 LLM。LLM 是无状态的,每次对话都是独立的,记忆是通过将整个对话上下文传递给 LLM 来实现的。

可以查看 memory 的内容。

print(memory.buffer)
print(memory.load_memory_variables({}))

可以得到类似这样的对话上下文。

Human: 你好,我的名字是 wwtg99
AI: 你好,wwtg99!很高兴认识你。我是你的人工智能助手,可以帮你解答问题和提供信息。请问有什么我可以帮助你的吗?
Human: 1 + 1 等于几?
AI: 1 + 1 等于 2。这是一个基本的数学加法问题,表示将两个相同的数值相加得到的结果。

{'history': 'Human: 你好,我的名字是 wwtg99\nAI: 你好,wwtg99!很高兴认识你。我是你的人工智能助手,可以帮你解答问题和提供信息。请问有什么我可以帮助你的吗?\nHuman: 1 + 1 等于几?\nAI: 1 + 1 等于 2。这是一个基本的数学加法问题,表示将两个相同的数值相加得到的结果。'}

我们也可以使用 save_context 来手动添加记忆。

memory = ConversationBufferMemory()
memory.save_context({"input": "你好"},
                    {"output": "有什么事"})
print("---第一次对话----")
print(memory.load_memory_variables({}))
memory.save_context({"input": "没什么事"},
                    {"output": "好的"})
print("---第二次对话----")
print(memory.load_memory_variables({}))

会得到类似如下输出。记忆会不断积累,好让 LLM 记得我们之前的对话内容。

---第一次对话----
{'history': 'Human: 你好\nAI: 有什么事'}
---第二次对话----
{'history': 'Human: 你好\nAI: 有什么事\nHuman: 没什么事\nAI: 好的'}

ConversationBufferWindowMemory

上面我们使用了 ConversationBufferMemory,还有其他类型的记忆。我们再来看看另一种记忆 ConversationBufferWindowMemory,它仅仅保留一个窗口的记忆。例如,如果设置 k = 1,那么只会记忆一次对话(我们提问一次和 LLM 回答一次)。

from langchain.memory import ConversationBufferWindowMemory

memory = ConversationBufferWindowMemory(k=1)
memory.save_context({"input": "你好"},
                    {"output": "有什么事"})
print("---第一次对话----")
print(memory.load_memory_variables({}))
memory.save_context({"input": "没什么事"},
                    {"output": "好的"})
print("---第二次对话----")
print(memory.load_memory_variables({}))

会得到这样的输出。

---第一次对话----
{'history': 'Human: 你好\nAI: 有什么事'}
---第二次对话----
{'history': 'Human: 没什么事\nAI: 好的'}

可以看到,由于 k = 1,因此只会保留一次对话的记忆,第二次对话就不会记得之前对话的内容了。同样的我们使用 ConversationBufferWindowMemory 且 k = 1,再来问 LLM 之前的三个问题(先报自己的名字,再问 1 + 1,再问我的名字是什么),由于 LLM 不记得之前的对话,它就不知道这个信息了。ConversationBufferWindowMemory 就好比人的记忆是有限的,不可能记得太久远的事情,因为太久远的对话内容可能对于当前的问题没有什么贡献。我们通过设置一个适当的 k 值,可以避免对话上下文无限扩大。

ConversationTokenBufferMemory

ConversationTokenBufferMemory 是另一种记忆实现,用来记忆有限数量的 tokens,tokens 和 LLM 的使用成本密切相关。关于 tokens 的概念,可以参考吴恩达的课程《给所有人的生成式 AI 课》 第二部分成本章节的介绍。

需要安装依赖 pip install tiktoken

from langchain.memory import ConversationTokenBufferMemory

llm = ChatOpenAI(temperature=0.0, model=llm_model)
memory = ConversationTokenBufferMemory(llm=llm, max_token_limit=50, base_url=base_url, api_key=api_key)
memory.save_context({"input": "AI is what?!"},
                    {"output": "Amazing!"})
memory.save_context({"input": "Backpropagation is what?"},
                    {"output": "Beautiful!"})
memory.save_context({"input": "Chatbots are what?"}, 
                    {"output": "Charming!"})
print(memory.load_memory_variables({}))

由于不同的 LLM 的 tokens 计算规则不同,因此我们需要使用 LLM 模型来创建 ConversationTokenBufferMemory。

ConversationSummaryMemory

相比于固定的对话次数或 tokens,ConversationSummaryMemory 提供了一种更适合较多内容的记忆实现,其对之前的对话进行总结并记忆。它是通过 LLM 将之前的对话进行总结压缩,提取关键信息,可以极大地减少对话上下文大小,同时尽可能地保留过去的记忆。

from langchain.memory import ConversationSummaryBufferMemory

memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100)

总结

LangChain 提供了多种记忆实现,每种记忆可用于不同的应用场景。

  • ConversationBufferMemory:按原样存储整个对话历史记录,但随着对话增多,提示词大小也会无限膨胀
  • ConversationBufferWindowMemory:保存最近一个固定窗口的对话,可以有效避免提示词过大,但过去的记忆依赖 k 的设置
  • ConversationTokenBufferMemory:使用 token 长度来限制最近保存的对话,可以有效地控制 LLM 的使用成本
  • ConversationSummaryMemory:不限制固定的对话数量,而是将过去的对话提取成摘要记忆,可以有效降低提示词大小,同时也保留了过去的记忆,可设定记忆留存最高的 tokens 长度

除了上面四种,LangChain 还支持其他的记忆实现。

  • 向量数据存储(Vector data memory):通过嵌入(Embeddings)将内容转换为词向量进行存储
  • 实体记忆存储(Entity memories):适用于特定的实体信息(如人、单位、地区等),将实体详细信息进行存储,在需要时查询

LangChain 支持组合多种记忆类型,可创建更全面和定制化的解决方案。例如,可以使用 ConversationBufferMemory 或 ConversationSummaryBufferMemory 来维护整体对话上下文,同时还利用实体记忆来存储来调用有关对话中提到的个人或对象实体的特定详细信息。

(未完待续)

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

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

相关文章

智能优化特征选择|基于鲸鱼WOA优化算法实现的特征选择研究Matlab程序(KNN分类器)

智能优化特征选择|基于鲸鱼WOA优化算法实现的特征选择研究Matlab程序(KNN分类器) 文章目录 一、基本原理原理流程举个例子总结 二、实验结果三、核心代码四、代码获取五、总结 智能优化特征选择|基于鲸鱼WOA优化算法实现的特征选择研究Matlab程序&#x…

Android 优化之 查找so 文件的来源

序言 有时候我们需要优化apk的包体积大小。比如下面这样的。一个so文件大小有10M。但是我们并不知道so文件是那个库引入的。所以需要研究一下。 方法 在参考网上现有方法,加上自己测试以后。有了下面的成功。而且在gradle 8.4.2都可以成功。相信大家都可以成功。…

MathType常见问题汇总

文章目录 MathType常见问题汇总一、如何将MathType内嵌到WPS工具栏中?二、在word中,如何批量修改所有MathType公式的字体以及大小格式?三、如何解决插入MathType公式后的行间距发生改变?参考 MathType常见问题汇总 一、如何将Mat…

CEASC:基于全局上下文增强的自适应稀疏卷积网络在无人机图像上的快速目标检测

Adaptive Sparse Convolutional Networks with Global Context Enhancement for Faster Object Detection on Drone Images 摘要 提出了一种基于稀疏卷积的探测头优化方法,该方法在精度和效率之间取得了较好的平衡。然而,该算法对微小物体的上下文信息融…

C/C++ JSON ORM

structs #include "json_struct.h" #include <vector>JS_ENUM(Error, None, InvalidRange, IllegalParam, Nullptr, OverLimitLen) JS_ENUM_DECLARE_STRING_PARSER(Error)// 搜索匹配区域 struct RangeContent {size_t start;size_t end;std::strin…

基于协同过滤与情感分析的酒店评论分析与景区推荐系统实现

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍系统界面推荐模块主题分类文本可视化每文一语 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 项目介绍 近年来&#xff0c;旅游行业风生水起&#…

#单片机高级 硬件部分笔记

课程内容 硬件基础知识PCB基础知识嘉立创EDA&#xff08;专业版&#xff09;软件的安装及使用PCB设计PCB设计规则&#xff08;原理图、布局、布线&#xff09;项目&#xff08;暂定&#xff09; 1、硬件基础 初级硬件工程师 熟练掌握数字电路、模拟电路知识&#xff0c;熟悉常用…

unity的问题记录(信息管理)

闭包 捕获引用&#xff1a;当你在委托或 lambda 表达式中使用外部变量时&#xff0c;它们捕获的是这个变量的引用&#xff0c;而不是当时的值。变量的生命周期&#xff1a;捕获的变量的生命周期不受限于它的作用域&#xff0c;委托可以在变量的作用域结束后继续访问它。 为了…

今晚8点直播预告——模拟RCT,真实世界研究新方法,快来了解一下吧

这是讲座预告&#xff0c;我们来向大家介绍一下真实世界研究的新方法—模拟RCT&#xff01; 郑老师喜欢交流&#xff0c;于是在2024年&#xff0c;决定邀请各位一起参加统计学沙龙&#xff0c;基本每周一期&#xff0c;欢迎各位朋友来交流、讲课。 本期沙龙&#xff0c;在8月29…

【ubuntu笔记】拉取docker镜像

拉取docker镜像 更换国内源 修改配置文件 sudo vim /etc/docker/daemon.json{"registry-mirrors": ["https://ustc-edu-cn.mirror.aliyuncs.com/","https://hub-mirror.c.163.com","https://mirror.baidubce.com","https://cc…

【Java】Maven多环境切换实战(实操图解)

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述四、解决方案&#xff1a;4.1 Maven多环境配置学习4.2 切换环境4.2.1 先打包4.2.2 之后可以切换 五、总结…

聊聊Netty异常传播链与最佳实践

写在文章开头 Netty通过责任链的思想解耦了各个业务的处理逻辑,是的用户可以非常方便的根据不同的生命周期进行相应的业务处理。而本文将针对Netty中的异常和异常传播过程进行分析,并给出最佳的处理技巧,希望对你有帮助。 Hi,我是 sharkChili ,是个不断在硬核技术上作死的…

Electron 项目实战 03: 实现一个截图功能

实现效果 实现思路 创建两个window&#xff0c;一个叫mainWindow&#xff0c;一个叫cutWindowmainWindow&#xff1a;主界面用来展示截图结果cutWindow&#xff1a;截图窗口&#xff0c;加载截图页面和截图交互逻辑mainWindow 页面点击截图&#xff0c;让cutWIndow 来实现具体…

WEB应用服务器TOMCAT知识点

TOMCAT介绍 Tomcat是一个开源的Java Web应用服务器&#xff0c;主要用于运行Java编写的网站。 Apache Tomcat是由Apache Software Foundation&#xff08;ASF&#xff09;开发的一个开源Java Web应用服务器&#xff0c;最初由Sun Microsystems捐赠给Apache软件基金会&#xf…

数据结构(Java实现):栈和队列相关练习题

文章目录 1. 题目链接2. 题目解析2.1 括号匹配2.2 逆波兰表达式求值2.3 出栈入栈次序匹配2.4 最小栈2.5 环形数组队列2.6 用队列实现栈2.7 用栈实现队列 1. 题目链接 括号匹配逆波兰表达式求值出栈入栈次序匹配最小栈设计循环队列用队列实现栈用栈实现队列 2. 题目解析 2.1 …

基于RK3568平台移植ffmpeg3.4.5及ffmpeg验证

目录 一、概述二、环境要求2.1 硬件环境2.2 软件环境三、移植流程3.1 编译x2643.2 编译mpp3.3 编译ffmpeg四、ffmpeg验证4.1 ffmpeg配置说明4.2 ffmpeg推流/拉流使用说明4.2.1 使用http方式推流/拉流4.2.1.1 先执行ffmpeg服务4.2.1.2 再执行ffmpeg进行推流4.2.1.3 最后执行vlc进…

等保测评中的数据安全保护:重点与挑战

在信息安全等级保护&#xff08;等保&#xff09;测评中&#xff0c;数据安全保护是核心关注点之一&#xff0c;它不仅关系到企业的合规性&#xff0c;还直接影响到企业的运营安全和用户信任。本文将深入探讨等保测评中数据安全保护的重点与挑战&#xff0c;为企业提供有效的应…

JavaScript初级——事件传播

1、事件的传播 关于事件的传播网景公司和微软公司有不同的理解&#xff1a; 微软公司认为事件应该是由内向外传播&#xff0c;也就是当事件触发时&#xff0c;应该先触发当前元素上的事件&#xff0c;然后再向当前元素的祖先元素上传播&#xff0c;也就说事件应该在冒泡阶段执行…

如何解决U盘无法压缩卷或删除卷的问题

U盘在日常使用中&#xff0c;偶尔会遇到无法压缩卷或删除卷的情况。出现这些问题通常与U盘的磁盘状态或文件系统有关。本文将介绍一种有效的解决方法&#xff0c;通过使用Windows自带的磁盘管理工具diskpart来解决这些问题。 一、问题原因 U盘无法压缩卷或删除卷的常见原因包…

Nginx部署Vue前端项目全攻略:从构建到上线一步到位!

要将前端 Vue 项目部署到 Nginx&#xff0c;你需要遵循以下步骤&#xff1a; 首先确保你已经安装了 Node.js 和 npm。如果没有&#xff0c;请访问 Node.js 官网 下载并安装。 使用 Vue CLI 创建一个新的 Vue 项目&#xff08;如果你还没有一个&#xff09;&#xff1a; npm i…