使用 MinIO 赢得 RAG 权利

人们常说,在人工智能时代,数据是你的护城河。为此,构建生产级 RAG 应用程序需要合适的数据基础架构来存储、版本控制、处理、评估和查询构成专有语料库的数据块。由于 MinIO 采用数据优先的 AI 方法,因此对于此类项目,我们默认的初始基础结构建议是设置现代数据湖 (MinIO) 和向量数据库。虽然在此过程中可能需要插入其他辅助工具,但这两个基础架构单元是基础。它们将作为随后在将 RAG 应用程序投入生产时遇到的几乎所有任务的重心。

但你陷入了一个难题。您以前听说过这些术语LLM和 RAG,但除此之外,由于未知,您并没有冒险太多。但是,如果有一个“Hello World”或样板应用程序可以帮助您入门,那不是很好吗?

别担心,我在同一条船上。因此,在这篇博客中,我们将演示如何使用 MinIO 使用商用硬件构建基于检索增强生成 (RAG) 的聊天应用程序。

  • 使用 MinIO 存储所有文档、处理过的块和使用矢量数据库的嵌入。

  • 使用 MinIO 的存储桶通知功能在向存储桶添加或删除文档时触发事件

  • Webhook,使用事件并使用 Langchain 处理文档,并将元数据和分块文档保存到元数据桶中

  • 为新添加或删除的分块文档触发 MinIO 存储桶通知事件

  • 一个 Webhook,它使用事件并生成嵌入并将其保存到 MinIO 中保存的向量数据库 (LanceDB)

使用的关键工具

  • MinIO - 用于持久化所有数据的对象存储

  • LanceDB - 将数据持久化在对象存储中的无服务器开源向量数据库

  • Ollama - 在本地运行LLM和嵌入模型(兼容 OpenAI API)

  • Gradio - 与 RAG 应用程序交互的接口

  • FastAPI - 用于接收来自 MinIO 的存储桶通知并公开 Gradio 应用程序的 Webhook 服务器

  • LangChain & Unstructured - 从我们的文档中提取有用的文本,并将它们分块进行嵌入

使用的型号

  • LLM - Phi-3-128K(3.8B参数)

  • 嵌入 - Nomic Embed Text v1.5 ( Matryoshka Embeddings/ 768 Dim, 8K context)

启动 MinIO 服务器

如果您还没有二进制文件,您可以从这里下载它

# Run MinIO detached

!minio server ~/dev/data --console-address :9090 &

启动Ollama Server + Download LLM & Embedding Model

从这里下载Ollama


# Start the Server

!ollama serve

# Download Phi-3 LLM

!ollama pull phi3:3.8b-mini-128k-instruct-q8_0

# Download Nomic Embed Text v1.5

!ollama pull nomic-embed-text:v1.5

# List All the Models

!ollama ls

使用 FastAPI 创建基本 Gradio 应用以测试模型

LLM_MODEL = "phi3:3.8b-mini-128k-instruct-q8_0"

EMBEDDING_MODEL = "nomic-embed-text:v1.5"

LLM_ENDPOINT = "http://localhost:11434/api/chat"

CHAT_API_PATH = "/chat"



def llm_chat(user_question, history):

    history = history or []

    user_message = f"**You**: {user_question}"

    llm_resp = requests.post(LLM_ENDPOINT,

                             json={"model": LLM_MODEL,

                                   "keep_alive""48h"# Keep the model in-memory for 48 hours

                                   "messages": [

                                       {"role""user",

                                        "content": user_question

                                        }

                                   ]},

                             stream=True)

    bot_response = "**AI:** "

    for resp in llm_resp.iter_lines():

        json_data = json.loads(resp)

        bot_response += json_data["message"]["content"]

        yield bot_response


import json

import gradio as gr

import requests

from fastapi import FastAPI, Request, BackgroundTasks

from pydantic import BaseModel

import uvicorn

import nest_asyncio


app = FastAPI()


with gr.Blocks(gr.themes.Soft()) as demo:

    gr.Markdown("## RAG with MinIO")

    ch_interface = gr.ChatInterface(llm_chat, undo_btn=None, clear_btn="Clear")

    ch_interface.chatbot.show_label = False

    ch_interface.chatbot.height = 600


demo.queue()

    


if __name__ == "__main__":

    nest_asyncio.apply()

    app = gr.mount_gradio_app(app, demo, path=CHAT_API_PATH)

    uvicorn.run(app, host="0.0.0.0", port=8808)

测试嵌入模型

import numpy as np


EMBEDDING_ENDPOINT = "http://localhost:11434/api/embeddings"

EMBEDDINGS_DIM = 768


def get_embedding(text):

    resp = requests.post(EMBEDDING_ENDPOINT,

                         json={"model": EMBEDDING_MODEL,

                               "prompt": text})

    return np.array(resp.json()["embedding"][:EMBEDDINGS_DIM], dtype=np.float16)

## Test with sample text

get_embedding("What is MinIO?")

引入管道概述

创建 MinIO 存储桶

使用 mc 命令或从 UI 执行此操作

  • custom-corpus - 存储所有文档

  • warehouse - 存储所有元数据、块和向量嵌入

!mc alias set 'myminio' 'http://localhost:9000' 'minioadmin' 'minioadmin'

!mc mb myminio/custom-corpus

!mc mb myminio/warehouse

创建从自定义语料库存储桶使用存储桶通知的 Webhook

import json

import gradio as gr

import requests

from fastapi import FastAPI, Request

from pydantic import BaseModel

import uvicorn

import nest_asyncio


app = FastAPI()


@app.post("/api/v1/document/notification")

async def receive_webhook(request: Request):

    json_data = await request.json()

    print(json.dumps(json_data, indent=2))


with gr.Blocks(gr.themes.Soft()) as demo:

    gr.Markdown("## RAG with MinIO")

    ch_interface = gr.ChatInterface(llm_chat, undo_btn=None, clear_btn="Clear")

    ch_interface.chatbot.show_label = False


demo.queue()

    


if __name__ == "__main__":

    nest_asyncio.apply()

    app = gr.mount_gradio_app(app, demo, path=CHAT_API_PATH)

    uvicorn.run(app, host="0.0.0.0", port=8808)

## Test with sample text

get_embedding("What is MinIO?")

创建 MinIO 事件通知并将其链接到 custom-corpus 存储桶

创建 Webhook 事件

在控制台中,转到 Events-> Add Event Destination -> Webhook

用以下值填写字段并点击保存

标识符 - doc-webhook

端点 - http://localhost:8808/api/v1/document/notification

单击顶部的 Restart MinIO (重新启动 MinIO) 时,将其设置为

(注意:您也可以为此使用 mc)

将 Webhook 事件链接到 custom-corpus bucket 事件

在控制台中,转到 Buckets (Administrator) -> custom-corpus -> Events

用以下值填写字段并点击保存

ARN - 从下拉列表中选择 doc-webhook

选择事件 - 选中 PUT 和 DELETE

(注意:您也可以为此使用 mc)

我们有了第一个 webhook 设置

现在通过添加和删除对象进行测试

从文档和区块中提取数据

我们将使用 Langchain 和 Unstructured 从 MinIO 读取对象,并将文档拆分为多个块

from langchain_text_splitters import RecursiveCharacterTextSplitter

from langchain_community.document_loaders import S3FileLoader


MINIO_ENDPOINT = "http://localhost:9000"

MINIO_ACCESS_KEY = "minioadmin"

MINIO_SECRET_KEY = "minioadmin"



# Split Text from a given document using chunk_size number of characters

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024,

                                               chunk_overlap=64,

                                               length_function=len)



def split_doc_by_chunks(bucket_name, object_key):

    loader = S3FileLoader(bucket_name,

                          object_key,

                          endpoint_url=MINIO_ENDPOINT,

                          aws_access_key_id=MINIO_ACCESS_KEY,

                          aws_secret_access_key=MINIO_SECRET_KEY)

    docs = loader.load()

    doc_splits = text_splitter.split_documents(docs)

    return doc_splits


# test the chunking

split_doc_by_chunks("custom-corpus""The-Enterprise-Object-Store-Feature-Set.pdf")

将分块逻辑添加到 Webhook

将块逻辑添加到 webhook 中,并将元数据和块保存到仓库存储桶中

import urllib.parse

import s3fs


METADATA_PREFIX = "metadata"


# Using s3fs to save and delete objects from MinIO

s3 = s3fs.S3FileSystem()



# Split the documents and save the metadata to warehouse bucket

def create_object_task(json_data):

    for record in json_data["Records"]:

        bucket_name = record["s3"]["bucket"]["name"]

        object_key = urllib.parse.unquote(record["s3"]["object"]["key"])

        print(record["s3"]["bucket"]["name"],

              record["s3"]["object"]["key"])


        doc_splits = split_doc_by_chunks(bucket_name, object_key)


        for i, chunk in enumerate(doc_splits):

            source = f"warehouse/{METADATA_PREFIX}/{bucket_name}/{object_key}/chunk_{i:05d}.json"

            with s3.open(source"w") as f:

                f.write(chunk.json())

    return "Task completed!"



def delete_object_task(json_data):

    for record in json_data["Records"]:

        bucket_name = record["s3"]["bucket"]["name"]

        object_key = urllib.parse.unquote(record["s3"]["object"]["key"])

        s3.delete(f"warehouse/{METADATA_PREFIX}/{bucket_name}/{object_key}", recursive=True)

    return "Task completed!"

使用新逻辑更新 FastAPI 服务器


import gradio as gr

import requests

from fastapi import FastAPI, Request, BackgroundTasks

from pydantic import BaseModel

import uvicorn

import nest_asyncio


app = FastAPI()


@app.post("/api/v1/document/notification")

async def receive_webhook(request: Request, background_tasks: BackgroundTasks):

json_data = await request.json()

if json_data["EventName"] == "s3:ObjectCreated:Put":

print("New object created!")

background_tasks.add_task(create_object_task, json_data)

if json_data["EventName"] == "s3:ObjectRemoved:Delete":

print("Object deleted!")

background_tasks.add_task(delete_object_task, json_data)

return {"status": "success"}


with gr.Blocks(gr.themes.Soft()) as demo:

gr.Markdown("## RAG with MinIO")

ch_interface = gr.ChatInterface(llm_chat, undo_btn=None, clear_btn="Clear")

ch_interface.chatbot.show_label = False


demo.queue()




if __name__ == "__main__":

nest_asyncio.apply()

app = gr.mount_gradio_app(app, demo, path=CHAT_API_PATH)

uvicorn.run(app, host="0.0.0.0", port=8808)

添加新的 Webhook 以处理文档元数据/块

现在我们有了第一个 webhook,下一步是获取所有带有元数据的块,生成嵌入并将其存储在向量数据库中

import json

import gradio as gr

import requests

from fastapi import FastAPI, Request, BackgroundTasks

from pydantic import BaseModel

import uvicorn

import nest_asyncio


app = FastAPI()




@app.post("/api/v1/metadata/notification")

async def receive_metadata_webhook(request: Request, background_tasks: BackgroundTasks):

    json_data = await request.json()

    print(json.dumps(json_data, indent=2))


@app.post("/api/v1/document/notification")

async def receive_webhook(request: Request, background_tasks: BackgroundTasks):

    json_data = await request.json()

    if json_data["EventName"] == "s3:ObjectCreated:Put":

        print("New object created!")

        background_tasks.add_task(create_object_task, json_data)

    if json_data["EventName"] == "s3:ObjectRemoved:Delete":

        print("Object deleted!")

        background_tasks.add_task(delete_object_task, json_data)

    return {"status""success"}


with gr.Blocks(gr.themes.Soft()) as demo:

    gr.Markdown("## RAG with MinIO")

    ch_interface = gr.ChatInterface(llm_chat, undo_btn=None, clear_btn="Clear")

    ch_interface.chatbot.show_label = False


demo.queue()

    


if __name__ == "__main__":

    nest_asyncio.apply()

    app = gr.mount_gradio_app(app, demo, path=CHAT_API_PATH)

    uvicorn.run(app, host="0.0.0.0", port=8808)

创建 MinIO 事件通知并将其链接到仓库存储桶

创建 Webhook 事件

在控制台中,转到 Events-> Add Event Destination -> Webhook

用以下值填写字段并点击保存

标识符 - metadata-webhook

端点 - http://localhost:8808/api/v1/metadata/notification

当出现提示时,单击顶部的重新启动 MinIO

(注意:您也可以为此使用 mc)

将 Webhook 事件链接到 custom-corpus bucket 事件

在控制台中,转到 Buckets (Administrator) -> warehouse -> Events

用以下值填写字段并点击保存

ARN - 从下拉列表中选择 metadata-webhook

前缀 - metadata/

后缀 - .json

选择事件 - 选中 PUT 和 DELETE

(注意:您也可以为此使用 mc)

我们有了第一个 webhook 设置

现在通过在自定义语料库中添加和删除对象进行测试,看看是否触发了此 Webhook

在 MinIO 中创建 LanceDB 向量数据库

现在我们已经有了基本的 webhook 工作,让我们在 MinIO 仓库桶中设置 lanceDB vector databse,我们将在其中保存所有嵌入和其他元数据字段

import os

import lancedb



# Set these environment variables for the lanceDB to connect to MinIO

os.environ["AWS_DEFAULT_REGION"] = "us-east-1"

os.environ["AWS_ACCESS_KEY_ID"] = MINIO_ACCESS_KEY

os.environ["AWS_SECRET_ACCESS_KEY"] = MINIO_SECRET_KEY

os.environ["AWS_ENDPOINT"] = MINIO_ENDPOINT

os.environ["ALLOW_HTTP"] = "True"



db = lancedb.connect("s3://warehouse/v-db/")

# list existing tables

db.table_names()
# Create a new table with pydantic schema

from lancedb.pydantic import LanceModel, Vector

import pyarrow as pa


DOCS_TABLE = "docs"

EMBEDDINGS_DIM = 768


table = None



class DocsModel(LanceModel):

    parent_source: str # Actual object/document source

    source: str # Chunk/Metadata source

    text: str # Chunked text

    vector: Vector(EMBEDDINGS_DIM, pa.float16()) # Vector to be stored



def get_or_create_table():

    global table

    if table is None and DOCS_TABLE not in list(db.table_names()):

        return db.create_table(DOCS_TABLE, schema=DocsModel)

    if table is None:

        table = db.open_table(DOCS_TABLE)

    return table


# Check if that worked

get_or_create_table()
# list existing tables

db.table_names()

将 lanceDB 中的存储/删除数据添加到 metadata-webhook

import multiprocessing


EMBEDDING_DOCUMENT_PREFIX = "search_document"


# Add queue that keeps the processed meteadata in memory

add_data_queue = multiprocessing.Queue()

delete_data_queue = multiprocessing.Queue()


def create_metadata_task(json_data):

    for record in json_data["Records"]:

        bucket_name = record["s3"]["bucket"]["name"]

        object_key = urllib.parse.unquote(record["s3"]["object"]["key"])

        print(bucket_name,

              object_key)

        with s3.open(f"{bucket_name}/{object_key}""r") as f:

            data = f.read()

            chunk_json = json.loads(data)

            embeddings = get_embedding(f"{EMBEDDING_DOCUMENT_PREFIX}: {chunk_json['page_content']}")

            add_data_queue.put({

                "text": chunk_json["page_content"],

                "parent_source": chunk_json.get("metadata""").get("source"""),

                "source": f"{bucket_name}/{object_key}",

                "vector": embeddings

            })

    return "Metadata Create Task Completed!"



def delete_metadata_task(json_data):

    for record in json_data["Records"]:

        bucket_name = record["s3"]["bucket"]["name"]

        object_key = urllib.parse.unquote(record["s3"]["object"]["key"])

        delete_data_queue.put(f"{bucket_name}/{object_key}")

    return "Metadata Delete Task completed!"

添加用于处理队列中数据的调度程序

from apscheduler.schedulers.background import BackgroundScheduler

import pandas as pd


def add_vector_job():

    data = []

    table = get_or_create_table()


    while not add_data_queue.empty():

        item = add_data_queue.get()

        data.append(item)


    if len(data) > 0:

        df = pd.DataFrame(data)

        table.add(df)

        table.compact_files()

        print(len(table.to_pandas()))



def delete_vector_job():

    table = get_or_create_table()

    source_data = []

    while not delete_data_queue.empty():

        item = delete_data_queue.get()

        source_data.append(item)

    if len(source_data) > 0:

        filter_data = ", ".join([f'"{d}"' for d in source_data])

        table.delete(f'source IN ({filter_data})')

        table.compact_files()

        table.cleanup_old_versions()

        print(len(table.to_pandas()))



scheduler = BackgroundScheduler()


scheduler.add_job(add_vector_job, 'interval', seconds=10)

scheduler.add_job(delete_vector_job, 'interval', seconds=10)

使用矢量嵌入更改更新 FastAPI

import json

import gradio as gr

import requests

from fastapi import FastAPI, Request, BackgroundTasks

from pydantic import BaseModel

import uvicorn

import nest_asyncio


app = FastAPI()



@app.on_event("startup")

async def startup_event():

    get_or_create_table()

    if not scheduler.running:

        scheduler.start()



@app.on_event("shutdown")

async def shutdown_event():

    scheduler.shutdown()


@app.post("/api/v1/metadata/notification")

async def receive_metadata_webhook(request: Request, background_tasks: BackgroundTasks):

    json_data = await request.json()

    if json_data["EventName"] == "s3:ObjectCreated:Put":

        print("New Metadata created!")

        background_tasks.add_task(create_metadata_task, json_data)

    if json_data["EventName"] == "s3:ObjectRemoved:Delete":

        print("Metadata deleted!")

        background_tasks.add_task(delete_metadata_task, json_data)

    return {"status""success"}


@app.post("/api/v1/document/notification")

async def receive_webhook(request: Request, background_tasks: BackgroundTasks):

    json_data = await request.json()

    if json_data["EventName"] == "s3:ObjectCreated:Put":

        print("New object created!")

        background_tasks.add_task(create_object_task, json_data)

    if json_data["EventName"] == "s3:ObjectRemoved:Delete":

        print("Object deleted!")

        background_tasks.add_task(delete_object_task, json_data)

    return {"status""success"}


with gr.Blocks(gr.themes.Soft()) as demo:

    gr.Markdown("## RAG with MinIO")

    ch_interface = gr.ChatInterface(llm_chat, undo_btn=None, clear_btn="Clear")

    ch_interface.chatbot.show_label = False

    ch_interface.chatbot.height = 600


demo.queue()

    


if __name__ == "__main__":

    nest_asyncio.apply()

    app = gr.mount_gradio_app(app, demo, path=CHAT_API_PATH)

    uvicorn.run(app, host="0.0.0.0", port=8808)

现在,我们已经有了引入管道,让我们集成最终的 RAG 管道。

添加矢量搜索功能

现在,我们已经将文档摄取到 lanceDB 中,让我们添加搜索功能

EMBEDDING_QUERY_PREFIX = "search_query"


def search(query, limit=5):

    query_embedding = get_embedding(f"{EMBEDDING_QUERY_PREFIX}: {query}")

    res = get_or_create_table().search(query_embedding).metric("cosine").limit(limit)

    return res
    
# Lets test to see if it works


res = search("What is MinIO Enterprise Object Store Lite?")

res.to_list()

提示LLM使用相关文件

RAG_PROMPT = """

DOCUMENT:

{documents}


QUESTION:

{user_question}


INSTRUCTIONS:

Answer in detail the user's QUESTION using the DOCUMENT text above.

Keep your answer ground in the facts of the DOCUMENT. Do not use sentence like "
The document states" citing the document.

If the DOCUMENT doesn't contain the facts to answer the QUESTION only Respond with "
Sorry! I Don't know"

"""
context_df = []


def llm_chat(user_question, history):

    history = history or []

    global context_df

    # Search for relevant document chunks

    res = search(user_question)

    documents = " ".join([d["text"].strip() for d in res.to_list()]) 

    # Pass the chunks to LLM for grounded response

    llm_resp = requests.post(LLM_ENDPOINT,

                             json={"model": LLM_MODEL,

                                   "messages": [

                                       {"role""user",

                                        "content": RAG_PROMPT.format(user_question=user_question, documents=documents)

                                        }

                                   ],

                                   "options": {

                                       # "temperature": 0,

                                       "top_p": 0.90,

                                   }},

                             stream=True)

    bot_response = "**AI:** "

    for resp in llm_resp.iter_lines():

        json_data = json.loads(resp)

        bot_response += json_data["message"]["content"]

        yield bot_response

    context_df = res.to_pandas()

    context_df = context_df.drop(columns=['source''vector'])



def clear_events():

    global context_df

    context_df = []

    return context_df

更新 FastAPI 聊天终端节点以使用 RAG

import json

import gradio as gr

import requests

from fastapi import FastAPI, Request, BackgroundTasks

from pydantic import BaseModel

import uvicorn

import nest_asyncio


app = FastAPI()



@app.on_event("startup")

async def startup_event():

    get_or_create_table()

    if not scheduler.running:

        scheduler.start()



@app.on_event("shutdown")

async def shutdown_event():

    scheduler.shutdown()


@app.post("/api/v1/metadata/notification")

async def receive_metadata_webhook(request: Request, background_tasks: BackgroundTasks):

    json_data = await request.json()

    if json_data["EventName"] == "s3:ObjectCreated:Put":

        print("New Metadata created!")

        background_tasks.add_task(create_metadata_task, json_data)

    if json_data["EventName"] == "s3:ObjectRemoved:Delete":

        print("Metadata deleted!")

        background_tasks.add_task(delete_metadata_task, json_data)

    return {"status""success"}


@app.post("/api/v1/document/notification")

async def receive_webhook(request: Request, background_tasks: BackgroundTasks):

    json_data = await request.json()

    if json_data["EventName"] == "s3:ObjectCreated:Put":

        print("New object created!")

        background_tasks.add_task(create_object_task, json_data)

    if json_data["EventName"] == "s3:ObjectRemoved:Delete":

        print("Object deleted!")

        background_tasks.add_task(delete_object_task, json_data)

    return {"status""success"}


with gr.Blocks(gr.themes.Soft()) as demo:

    gr.Markdown("## RAG with MinIO")

    ch_interface = gr.ChatInterface(llm_chat, undo_btn=None, clear_btn="Clear")

    ch_interface.chatbot.show_label = False

    ch_interface.chatbot.height = 600

    gr.Markdown("### Context Supplied")

    context_dataframe = gr.DataFrame(headers=["parent_source""text""_distance"], wrap=True)

    ch_interface.clear_btn.click(clear_events, [], context_dataframe)


    @gr.on(ch_interface.output_components, inputs=[ch_interface.chatbot], outputs=[context_dataframe])

    def update_chat_context_df(text):

        global context_df

        if context_df is not None:

            return context_df

        return ""


demo.queue()

    


if __name__ == "__main__":

    nest_asyncio.apply()

    app = gr.mount_gradio_app(app, demo, path=CHAT_API_PATH)

    uvicorn.run(app, host="0.0.0.0", port=8808)

RAGs-R-Us

作为 MinIO 专注于 AI 集成的开发人员,我一直在探索如何将我们的工具无缝集成到现代 AI 架构中,以提高效率和可扩展性。在本文中,我们向您展示了如何将 MinIO 与检索增强生成 (RAG) 集成以构建聊天应用程序。这只是冰山一角,可以推动您为 RAG 和 MinIO 构建更多独特的用例。现在,您已经具备了执行此操作的构建块。让我们开始吧!

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

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

相关文章

PostMan添加path参数请求

如下图: /findInventoryByQrCode/:qrCode用 : 会出现Path Variables 栏

科普文:看懂Linux日志分析

日志文件是Linux系统中极为重要的一部分,它们记录了系统和应用程序的各种活动信息。通过日志文件,系统管理员可以监控系统的运行状态、发现潜在的问题,并进行故障排除。 一. 常见的日志文件 在介绍具体的日志分析命令之前,首先了…

Mybatis的优缺点及适用场景?

目录 一、什么是Mybatis? 二、Mybatis框架的特点 三、Mybatis框架的优点? 四、MyBatis 框架的缺点? 五、MyBatis 框架适用场合? 六、代码示例 1. 配置文件 mybatis-config.xml 2. 映射文件 UserMapper.xml 3. Java 代码…

前端面试39(关于git)

针对前端开发者的Git面试题可以覆盖Git的基础概念、常用命令、工作流程、团队协作、以及解决冲突等方面。以下是一些具体的Git面试 Git基础知识 什么是Git? Git是一个分布式版本控制系统,用于跟踪计算机文件的更改,并协调多个人共同在一个项…

c++ 多边形 xyz 数据 获取 中心点方法,线的中心点取中心值搞定 已解决

有需求需要对。多边形 获取中心点方法&#xff0c;绝大多数都是 puthon和java版本。立体几何学中的知识。 封装函数 point ##########::getCenterOfGravity(std::vector<point> polygon) {if (polygon.size() < 2)return point();auto Area [](point p0, point p1, p…

C#知识|账号管理系统:UI层-添加账号窗体设计思路及流程。

哈喽,你好啊,我是雷工! 前边练习过详情页窗体的设计思路及流程: 《C#知识|上位机UI设计-详情窗体设计思路及流程(实例)》 本节练习添加账号窗体的UI设计,以下为学习笔记。 01 效果展示 02 添加窗体 在UI层添加Windows窗体,设置名称为:FrmAddAcount.cs 设置窗体属…

检测管道有没有水的传感器-管道液位传感器

如今&#xff0c;随着生活方式的多样化和科技的进步&#xff0c;检测管道液位的需求变得愈发重要。特别是在诸如扫地机器人、洗地机、饮水机等设备中&#xff0c;确保管道中是否存在水是关键的功能之一。针对这一需求&#xff0c;市场上涌现出多种先进的管道液位传感器&#xf…

移动应用:商城购物类,是最常见的,想出彩或许就差灵犀一指

在移动应用中&#xff0c;商城购物类的非常常见&#xff0c;模式也非常成熟&#xff0c;想要设计的出彩也是有难度的&#xff0c;这次分享一些不同的。

项目收获总结--Redis的知识收获

一、概述 最近几天公司项目开发上线完成&#xff0c;做个收获总结吧~ 今天记录Redis的收获和提升。 二、Redis异步队列 Redis做异步队列一般使用 list 结构作为队列&#xff0c;rpush 生产消息&#xff0c;lpop 消费消息。当 lpop 没有消息的时候&#xff0c;要适当sleep再…

迁移至 AI-Ready 基础架构:日立内容平台至 MinIO

借助我们的 HCP-to-MinIO 工具&#xff0c;从 Hitachi Content Platform &#xff08;HCP&#xff09; 过渡到 MinIO 从未如此简单。该工具旨在支持客户不断变化的存储需求&#xff0c;可在 GitHub 上免费获得&#xff0c;大大简化了迁移过程。许多组织正在转型&#xff0c;以利…

supOS助力油气行业数智化转型

在油气行业&#xff0c;高温高压、易燃易爆的特殊环境对生产安全和效率提出了极高的要求。传统工厂管理模式往往存在信息孤岛、决策滞后、响应速度慢等问题&#xff0c;难以适应现代工业化发展的需求。 从传统工厂到智能工厂&#xff0c;首先要实现企业经营运营自动化和生产过程…

新声创新20年:无线技术给助听器插上“娱乐”的翅膀

听力损失并非现代人的专利&#xff0c;古代人也会有听力损失。助听器距今发展已经有二百多年了&#xff0c;从当初单纯的声音放大器到如今的全数字时代助听器&#xff0c;助听器发生了翻天覆地的变化&#xff0c;现代助听器除了助听功能&#xff0c;还具有看电视&#xff0c;听…

uni-app/vue项目如何封装全局消息提示组件

效果图&#xff1a; 第一步&#xff1a;封装组件和方法&#xff0c;采用插件式注册&#xff01; 在项目目录下新建components文件夹&#xff0c;里面放两个文件&#xff0c;分别是index.vue和index.js. index.vue&#xff1a; <template><div class"toast&quo…

漏洞挖掘思路分享 | 首次尝试cnvd捡洞

因为那天项目刚忙完闲来无事&#xff0c;尝试捡个cnvd洞&#xff0c;cnvd录取要五千万资产&#xff0c;自己又懒得找毕竟捡洞嘛&#xff0c;索性去cnvd上进行搜集 直接开搜弱口令&#xff0c;因为我比较喜欢有登录框的站&#xff0c;这样搜索出来的资产可能就更容易进行挖掘 随…

闲鱼电商API接口:精确搜索商品详情的利器

随着互联网的快速发展&#xff0c;电商平台日益普及&#xff0c;用户对购物体验的要求也越来越高。在众多电商平台上&#xff0c;闲鱼作为国内领先的二手交易平台&#xff0c;用户量庞大&#xff0c;商品种类繁多&#xff0c;为了提高用户购物体验&#xff0c;联讯数据闲鱼推出…

【工具】咸鱼小助手,一款咸鱼之王辅助工具

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ Github&#xff1a;咸鱼之王的自动化脚本&#xff0c;自动答题、爬塔、领资源等 下载&#xff1a;(密码:9u22) 咸鱼小助手 文档&#xff1a;腾讯文档 视…

如何才能半个月学习完自动化测试,成功跳槽涨薪?

一直想写类似的文章&#xff0c;但是一直没有时间&#xff0c;正好乘此周末&#xff0c;终于可以写写我的见解了<关于如何学习自动化测试>&#xff0c;其实自动化测试的知识很宽泛&#xff0c;具体细分又可以分为&#xff1a;Web自动化测试&#xff08;PythonSelenium&am…

递归(五)—— 初识暴力递归之“如何利用递归实现栈逆序”

题目&#xff1a;要求不使用额外的数据结构&#xff0c;仅利用递归函数实现栈的逆序。 题目分析&#xff1a; 利用实例来理解题意&#xff0c;栈内元素从栈底到栈顶一次是3&#xff0c;2&#xff0c;1 &#xff0c;要求经过处理后&#xff0c;栈底到栈顶依次是1&#xff0c;2…

OpenCV中使用Canny算法在图像中查找边缘

操作系统&#xff1a;ubuntu22.04OpenCV版本&#xff1a;OpenCV4.9IDE:Visual Studio Code编程语言&#xff1a;C11 算法描述 Canny算法是一种广泛应用于计算机视觉和图像处理领域中的边缘检测算法。它由John F. Canny在1986年提出&#xff0c;旨在寻找给定噪声条件下的最佳边…

NAS免费用,鲁大师 AiNAS正式发布,「专业版」年卡仅需264元

7月10日&#xff0c;鲁大师召开新品发布会&#xff0c;正式发布旗下以“提供本地Ai部署和使用能力以及在线NAS功能”并行的复合软件产品&#xff1a;鲁大师 AiNAS。 全新的鲁大师 AiNAS将持续满足现如今大众对于数字化生活的全新需求&#xff0c;将“云存储”的便捷与NAS的大容…