【InternLM 實戰營筆記】基于 InternLM 和 LangChain 搭建你的知識庫

準備環境

bash
/root/share/install_conda_env_internlm_base.sh InternLM

升級PIP

# 升級pip
python -m pip install --upgrade pippip install modelscope==1.9.5
pip install transformers==4.35.2
pip install streamlit==1.24.0
pip install sentencepiece==0.1.99
pip install accelerate==0.24.1

模型下載

mkdir -p /root/data/model/Shanghai_AI_Laboratory
cp -r /root/share/temp/model_repos/internlm-chat-7b /root/data/model/Shanghai_AI_Laboratory/internlm-chat-7b

LangChain 相關環境配置

安裝依賴

pip install langchain==0.0.292
pip install gradio==4.4.0
pip install chromadb==0.4.15
pip install sentence-transformers==2.2.2
pip install unstructured==0.10.30
pip install markdown==3.3.7

需要使用 huggingface 官方提供的 huggingface-cli 命令行工具。安裝依賴:

pip install -U huggingface_hub

下載:

import os# 下載模型
os.system('huggingface-cli download --resume-download sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 --local-dir /root/data/model/sentence-transformer')

使用鏡像下載代碼:

import os# 設置環境變量
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'# 下載模型
os.system('huggingface-cli download --resume-download sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 --local-dir /root/data/model/sentence-transformer')

下載過程
在這里插入圖片描述

下載 NLTK 相關資源

cd /root
git clone https://gitee.com/yzy0612/nltk_data.git  --branch gh-pages
cd nltk_data
mv packages/*  ./
cd tokenizers
unzip punkt.zip
cd ../taggers
unzip averaged_perceptron_tagger.zip

下載本項目代碼

cd /root/data
git clone https://github.com/InternLM/tutorial

知識庫搭建

我們選擇由上海人工智能實驗室開源的一系列大模型工具開源倉庫作為語料庫來源,包括:

  • OpenCompass:面向大模型評測的一站式平臺
  • IMDeploy:涵蓋了 LLM 任務的全套輕量化、部署和服務解決方案的高效推理工具箱
  • XTuner:輕量級微調大語言模型的工具庫
  • InternLM-XComposer:浦語·靈筆,基于書生·浦語大語言模型研發的視覺-語言大模型
  • Lagent:一個輕量級、開源的基于大語言模型的智能體(agent)框架
    ( InternLM:一個開源的輕量級訓練框架,旨在支持大模型訓練而無需大量的依賴

將上述遠程開源倉庫 Clone 到本地

# 進入到數據庫盤
cd /root/data
# clone 上述開源倉庫
git clone https://gitee.com/open-compass/opencompass.git
git clone https://gitee.com/InternLM/lmdeploy.git
git clone https://gitee.com/InternLM/xtuner.git
git clone https://gitee.com/InternLM/InternLM-XComposer.git
git clone https://gitee.com/InternLM/lagent.git
git clone https://gitee.com/InternLM/InternLM.git

接著,為語料處理方便,我們將選用上述倉庫中所有的 markdown、txt 文件作為示例語料庫。注意,也可以選用其中的代碼文件加入到知識庫中,但需要針對代碼文件格式進行額外處理(因為代碼文件對邏輯聯系要求較高,且規范性較強,在分割時最好基于代碼模塊進行分割再加入向量數據庫)。

我們首先將上述倉庫中所有滿足條件的文件路徑找出來,我們定義一個函數,該函數將遞歸指定文件夾路徑,返回其中所有滿足條件(即后綴名為 .md 或者 .txt 的文件)的文件路徑:

import os 
def get_files(dir_path):# args:dir_path,目標文件夾路徑file_list = []for filepath, dirnames, filenames in os.walk(dir_path):# os.walk 函數將遞歸遍歷指定文件夾for filename in filenames:# 通過后綴名判斷文件類型是否滿足要求if filename.endswith(".md"):# 如果滿足要求,將其絕對路徑加入到結果列表file_list.append(os.path.join(filepath, filename))elif filename.endswith(".txt"):file_list.append(os.path.join(filepath, filename))return file_list

加載數據

from tqdm import tqdm
from langchain.document_loaders import UnstructuredFileLoader
from langchain.document_loaders import UnstructuredMarkdownLoaderdef get_text(dir_path):# args:dir_path,目標文件夾路徑# 首先調用上文定義的函數得到目標文件路徑列表file_lst = get_files(dir_path)# docs 存放加載之后的純文本對象docs = []# 遍歷所有目標文件for one_file in tqdm(file_lst):file_type = one_file.split('.')[-1]if file_type == 'md':loader = UnstructuredMarkdownLoader(one_file)elif file_type == 'txt':loader = UnstructuredFileLoader(one_file)else:# 如果是不符合條件的文件,直接跳過continuedocs.extend(loader.load())return docs

構建向量數據庫

from langchain.text_splitter import RecursiveCharacterTextSplittertext_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=150)
split_docs = text_splitter.split_documents(docs)

接著我們選用開源詞向量模型 Sentence Transformer 來進行文本向量化。LangChain 提供了直接引入 HuggingFace 開源社區中的模型進行向量化的接口:

from langchain.embeddings.huggingface import HuggingFaceEmbeddingsembeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")

同時,考慮到 Chroma 是目前最常用的入門數據庫,我們選擇 Chroma 作為向量數據庫,基于上文分塊后的文檔以及加載的開源向量化模型,將語料加載到指定路徑下的向量數據庫:

from langchain.vectorstores import Chroma# 定義持久化路徑
persist_directory = 'data_base/vector_db/chroma'
# 加載數據庫
vectordb = Chroma.from_documents(documents=split_docs,embedding=embeddings,persist_directory=persist_directory  # 允許我們將persist_directory目錄保存到磁盤上
)
# 將加載的向量數據庫持久化到磁盤上
vectordb.persist()

整體腳本

# 首先導入所需第三方庫
from langchain.document_loaders import UnstructuredFileLoader
from langchain.document_loaders import UnstructuredMarkdownLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from tqdm import tqdm
import os# 獲取文件路徑函數
def get_files(dir_path):# args:dir_path,目標文件夾路徑file_list = []for filepath, dirnames, filenames in os.walk(dir_path):# os.walk 函數將遞歸遍歷指定文件夾for filename in filenames:# 通過后綴名判斷文件類型是否滿足要求if filename.endswith(".md"):# 如果滿足要求,將其絕對路徑加入到結果列表file_list.append(os.path.join(filepath, filename))elif filename.endswith(".txt"):file_list.append(os.path.join(filepath, filename))return file_list# 加載文件函數
def get_text(dir_path):# args:dir_path,目標文件夾路徑# 首先調用上文定義的函數得到目標文件路徑列表file_lst = get_files(dir_path)# docs 存放加載之后的純文本對象docs = []# 遍歷所有目標文件for one_file in tqdm(file_lst):file_type = one_file.split('.')[-1]if file_type == 'md':loader = UnstructuredMarkdownLoader(one_file)elif file_type == 'txt':loader = UnstructuredFileLoader(one_file)else:# 如果是不符合條件的文件,直接跳過continuedocs.extend(loader.load())return docs# 目標文件夾
tar_dir = ["/root/data/InternLM","/root/data/InternLM-XComposer","/root/data/lagent","/root/data/lmdeploy","/root/data/opencompass","/root/data/xtuner"
]# 加載目標文件
docs = []
for dir_path in tar_dir:docs.extend(get_text(dir_path))# 對文本進行分塊
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=150)
split_docs = text_splitter.split_documents(docs)# 加載開源詞向量模型
embeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")# 構建向量數據庫
# 定義持久化路徑
persist_directory = 'data_base/vector_db/chroma'
# 加載數據庫
vectordb = Chroma.from_documents(documents=split_docs,embedding=embeddings,persist_directory=persist_directory  # 允許我們將persist_directory目錄保存到磁盤上
)
# 將加載的向量數據庫持久化到磁盤上
vectordb.persist()

在 /root/data 下新建一個 demo目錄,將該腳本和后續腳本均放在該目錄下運行。運行上述腳本,即可在本地構建已持久化的向量數據庫,后續直接導入該數據庫即可,無需重復構建。

腳本執行過程
在這里插入圖片描述

InternLM 接入 LangChain

from langchain.llms.base import LLM
from typing import Any, List, Optional
from langchain.callbacks.manager import CallbackManagerForLLMRun
from transformers import AutoTokenizer, AutoModelForCausalLM
import torchclass InternLM_LLM(LLM):# 基于本地 InternLM 自定義 LLM 類tokenizer : AutoTokenizer = Nonemodel: AutoModelForCausalLM = Nonedef __init__(self, model_path :str):# model_path: InternLM 模型路徑# 從本地初始化模型super().__init__()print("正在從本地加載模型...")self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)self.model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True).to(torch.bfloat16).cuda()self.model = self.model.eval()print("完成本地模型的加載")def _call(self, prompt : str, stop: Optional[List[str]] = None,run_manager: Optional[CallbackManagerForLLMRun] = None,**kwargs: Any):# 重寫調用函數system_prompt = """You are an AI assistant whose name is InternLM (書生·浦語).- InternLM (書生·浦語) is a conversational language model that is developed by Shanghai AI Laboratory (上海人工智能實驗室). It is designed to be helpful, honest, and harmless.- InternLM (書生·浦語) can understand and communicate fluently in the language chosen by the user such as English and 中文."""messages = [(system_prompt, '')]response, history = self.model.chat(self.tokenizer, prompt , history=messages)return response@propertydef _llm_type(self) -> str:return "InternLM"

上述類定義中,我們分別重寫了構造函數和 _call 函數:對于構造函數,我們在對象實例化的一開始加載本地部署的 InternLM 模型,從而避免每一次調用都需要重新加載模型帶來的時間過長;_call 函數是 LLM 類的核心函數,LangChain 會調用該函數來調用 LLM,在該函數中,我們調用已實例化模型的 chat 方法,從而實現對模型的調用并返回調用結果。

在整體項目中,我們將上述代碼封裝為 LLM.py,后續將直接從該文件中引入自定義的 LLM 類。

構建檢索問答鏈

加載向量數據庫

from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
import os# 定義 Embeddings
embeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")# 向量數據庫持久化路徑
persist_directory = 'data_base/vector_db/chroma'# 加載數據庫
vectordb = Chroma(persist_directory=persist_directory, embedding_function=embeddings
)

實例化自定義 LLM 與 Prompt Template

from LLM import InternLM_LLM
llm = InternLM_LLM(model_path = "/root/data/model/Shanghai_AI_Laboratory/internlm-chat-7b")
llm.predict("你是誰")
構建檢索問答鏈,還需要構建一個 Prompt Template,該 Template 其實基于一個帶變量的字符串,在檢索之后,LangChain 會將檢索到的相關文檔片段填入到 Template 的變量中,從而實現帶知識的 Prompt 構建。我們可以基于 LangChain 的 Template 基類來實例化這樣一個 Template 對象:from langchain.prompts import PromptTemplate# 我們所構造的 Prompt 模板
template = """使用以下上下文來回答用戶的問題。如果你不知道答案,就說你不知道。總是使用中文回答。
問題: {question}
可參考的上下文:
···
{context}
···
如果給定的上下文無法讓你做出回答,請回答你不知道。
有用的回答:"""# 調用 LangChain 的方法來實例化一個 Template 對象,該對象包含了 context 和 question 兩個變量,在實際調用時,這兩個變量會被檢索到的文檔片段和用戶提問填充
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],template=template)

構建檢索問答鏈

from langchain.chains import RetrievalQAqa_chain = RetrievalQA.from_chain_type(llm,retriever=vectordb.as_retriever(),return_source_documents=True,chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})

得到的 qa_chain 對象即可以實現我們的核心功能,即基于 InternLM 模型的專業知識庫助手。我們可以對比該檢索問答鏈和純 LLM 的問答效果:

# 檢索問答鏈回答效果
question = "什么是InternLM"
result = qa_chain({"query": question})
print("檢索問答鏈回答 question 的結果:")
print(result["result"])# 僅 LLM 回答效果
result_2 = llm(question)
print("大模型回答 question 的結果:")
print(result_2)

運行結果
在這里插入圖片描述

部署 Web Demo

from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
import os
from LLM import InternLM_LLM
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQAdef load_chain():# 加載問答鏈# 定義 Embeddingsembeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")# 向量數據庫持久化路徑persist_directory = 'data_base/vector_db/chroma'# 加載數據庫vectordb = Chroma(persist_directory=persist_directory,  # 允許我們將persist_directory目錄保存到磁盤上embedding_function=embeddings)# 加載自定義 LLMllm = InternLM_LLM(model_path = "/root/data/model/Shanghai_AI_Laboratory/internlm-chat-7b")# 定義一個 Prompt Templatetemplate = """使用以下上下文來回答最后的問題。如果你不知道答案,就說你不知道,不要試圖編造答案。盡量使答案簡明扼要。總是在回答的最后說“謝謝你的提問!”。{context}問題: {question}有用的回答:"""QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],template=template)# 運行 chainqa_chain = RetrievalQA.from_chain_type(llm,retriever=vectordb.as_retriever(),return_source_documents=True,chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})return qa_chain

接著我們定義一個類,該類負責加載并存儲檢索問答鏈,并響應 Web 界面里調用檢索問答鏈進行回答的動作:

class Model_center():"""存儲檢索問答鏈的對象 """def __init__(self):# 構造函數,加載檢索問答鏈self.chain = load_chain()def qa_chain_self_answer(self, question: str, chat_history: list = []):"""調用問答鏈進行回答"""if question == None or len(question) < 1:return "", chat_historytry:chat_history.append((question, self.chain({"query": question})["result"]))# 將問答結果直接附加到問答歷史中,Gradio 會將其展示出來return "", chat_historyexcept Exception as e:return e, chat_history

然后我們只需按照 Gradio 的框架使用方法,實例化一個 Web 界面并將點擊動作綁定到上述類的回答方法即可:

import gradio as gr# 實例化核心功能對象
model_center = Model_center()
# 創建一個 Web 界面
block = gr.Blocks()
with block as demo:with gr.Row(equal_height=True):   with gr.Column(scale=15):# 展示的頁面標題gr.Markdown("""<h1><center>InternLM</center></h1><center>書生浦語</center>""")with gr.Row():with gr.Column(scale=4):# 創建一個聊天機器人對象chatbot = gr.Chatbot(height=450, show_copy_button=True)# 創建一個文本框組件,用于輸入 prompt。msg = gr.Textbox(label="Prompt/問題")with gr.Row():# 創建提交按鈕。db_wo_his_btn = gr.Button("Chat")with gr.Row():# 創建一個清除按鈕,用于清除聊天機器人組件的內容。clear = gr.ClearButton(components=[chatbot], value="Clear console")# 設置按鈕的點擊事件。當點擊時,調用上面定義的 qa_chain_self_answer 函數,并傳入用戶的消息和聊天歷史記錄,然后更新文本框和聊天機器人組件。db_wo_his_btn.click(model_center.qa_chain_self_answer, inputs=[msg, chatbot], outputs=[msg, chatbot])gr.Markdown("""提醒:<br>1. 初始化數據庫時間可能較長,請耐心等待。2. 使用中如果出現異常,將會在文本輸入框進行展示,請不要驚慌。 <br>""")
gr.close_all()
# 直接啟動
demo.launch()

運行界面
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/712228.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/712228.shtml
英文地址,請注明出處:http://en.pswp.cn/news/712228.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL 多表查詢 連接查詢 外連接

介紹 MySQL 多表查詢 連接查詢 內連接 外連接分為兩種&#xff0c;左外和右外連接&#xff0c; 左外&#xff1a;相當于查詢表1(左表)的所有數據 包含 表1和表2交集部分的數據,完全包含左表的數據 右外&#xff1a;相當于查詢表2(右表)的所有數據 包含 表1和表2交集部分的數據…

比特幣暴漲逼近歷史最高點;阿里云全線降價20%丨 RTE 開發者日報 Vol.155

開發者朋友們大家好&#xff1a; 這里是 「RTE 開發者日報」 &#xff0c;每天和大家一起看新聞、聊八卦。我們的社區編輯團隊會整理分享 RTE &#xff08;Real Time Engagement&#xff09; 領域內「有話題的 新聞 」、「有態度的 觀點 」、「有意思的 數據 」、「有思考的 文…

mysql查詢某個庫下所有表的數據量

要查詢MySQL數據庫下指定數據庫的所有表的數據量&#xff08;即每個表中的記錄數&#xff09;&#xff0c;你可以使用以下步驟&#xff1a; 連接到MySQL數據庫&#xff1a;首先&#xff0c;你需要使用MySQL客戶端或任何支持MySQL連接的編程語言&#xff08;如Python, PHP, Nod…

adb命名大全

1. 獲取內部版本號&#xff1a; adb shell getprop ro.build.display.innerver 2. 獲取按鍵值&#xff1a; adb shell getevent 3. 獲取apk信息&#xff1a; adb shell dumpsys package 包名 ->info.txt 4. 獲取應用包名&#xff1a;adb shell dumpsys window windows | gre…

男頻和女頻的區別是什么?

男頻是我去找權力。女頻是權力來找我。 男頻不管是什么類型的&#xff0c;核心大抵都是接近權力&#xff0c;干掉權力&#xff0c;成為強權。 開局男主角弱小&#xff0c;被人嘲笑&#xff0c;被人瞧不起&#xff0c;父母親人連帶著沒地位&#xff0c;欠錢&#xff0c;被冤枉&a…

算法D32 | 貪心算法2 | 122.買賣股票的最佳時機II 55. 跳躍游戲 45.跳躍游戲II

122.買賣股票的最佳時機II 本題解法很巧妙&#xff0c;大家可以看題思考一下&#xff0c;在看題解。 代碼隨想錄P 只收集每天的正利潤&#xff0c;利潤可以每天分解。 Python: class Solution:def maxProfit(self, prices: List[int]) -> int:if len(prices)<2: retur…

C++的晨曦之旅:開啟編程的新篇章

個人主頁&#xff1a;日刷百題 系列專欄&#xff1a;〖C/C小游戲〗〖Linux〗〖數據結構〗 〖C語言〗 &#x1f30e;歡迎各位→點贊&#x1f44d;收藏??留言&#x1f4dd; ? ? 一、 命名空間 在 C/C 中&#xff0c;變量、函數和后面要學到的類都是大量存在的&#xff0…

龍躍金三銀四,程序員如何翻云覆雨贏取心儀offer

春天的腳步漸近&#xff0c;萬物復蘇&#xff0c;生機盎然。對于許多程序員來說&#xff0c;一年中最繁忙、最重要的面試季節也隨之而來。金三銀四&#xff0c;即三月和四月&#xff0c;被廣大程序員視為求職的黃金時期。在這兩個月里&#xff0c;各大公司紛紛開放招聘&#xf…

div在vue的組件之中如何設置這個字體的顏色和樣式大小

在Vue組件中設置<div>的字體顏色和樣式大小可以通過兩種主要方式實現&#xff1a;通過內聯樣式&#xff08;inline styles&#xff09;或者通過CSS類&#xff08;CSS classes&#xff09;。 使用內聯樣式 在Vue模板中直接在元素上使用style屬性來設置樣式。這種方法適用…

Android Shadow插件化框架分析與集成(一)

一、shadow源碼導入及分析 1、下載項目源碼 2、導入到Android studio 3、設置jdk及sdk版本 包/應用描述類型sample-constant公共字符串常量libsample-host宿主應用applicationsample-host-lib宿主應用依賴包libsample-manager是插件管理器的動態實現,主要負責加載插件和安裝…

【Android開發】01-第一個Android APP

一、改MainActivity class MainActivity : AppCompatActivity() {/*因Android的app有生命周期&#xff0c;故入口是OnCreate而不是main函數*/override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main…

071:vue+cesium 實現下雨效果

第071個 點擊查看專欄目錄 本示例的目的是介紹如何在vue+cesium中實現下雨效果,這里使用著色器來實現實例特效。 直接復制下面的 vue+cesium源代碼,操作2分鐘即可運行實現效果. 文章目錄 示例效果配置方式示例源代碼(共120行)著色代碼實現心得:專欄目標示例效果

【筆記】深度學習入門:基于Python的理論與實現(六)

深度學習 深度學習是加深了層的深度神經網絡 加深網絡 本節我們將這些已經學過的技術匯總起來&#xff0c;創建一個深度網絡&#xff0c;挑戰 MNIST 數據集的手寫數字識別 向更深的網絡出發 基于33的小型濾波器的卷積層。激活函數是ReLU。全連接層的后面使用Dropout層。基…

初階數據結構:棧與隊列的擴展補充

目錄 1. 棧與隊列練習題1.1 棧的括號匹配問題1.2 用隊列來實現棧1.3 用棧來實現隊列1.4 擴展&#xff1a;循環隊列 1. 棧與隊列練習題 1.1 棧的括號匹配問題 題目信息&#xff1a; 題目鏈接&#xff1a; 括號匹配問題 思路&#xff1a; 利用棧的后進先出特性來實現括號的匹配 …

網絡編程day3

1.思維導圖 2.TCP機械臂測試 tcpCli.c #include<myhead.h> #define SER_IP "192.168.125.162" //服務器IP #define SER_PORT 7777 //服務器端口#define CLI_IP "192.168.159.144" //客戶端IP #define CLI_PORT 9999 //客戶端端口號int…

婚姻情感 17

婚姻情感 17 怎么和女生聊天&#xff1f;讓對方感興趣給對方好體驗深層次的聊天刷心疼長得帥和強大的區別從我到我們 追女生的思路 怎么和女生聊天&#xff1f; 在和女生互動的時候就很難去進入一種很深層次的一個連接&#xff0c;就是說很多時候我們和女生互動總是停留在第三…

底層自行實現——監督學習算法(1線性回歸)

1.1 簡單線性回歸 1. 簡介 簡單線性回歸&#xff08;SLR - Simple Linear Regression&#xff09;模型可以表示為&#xff1a; Y β 0 β 1 X ? Y \beta_0 \beta_1X \epsilon Yβ0?β1?X? Y Y Y&#xff1a;因變量或目標變量。 X X X&#xff1a;自變量或解釋變量。…

考取ORACLE數據庫OCP的必要性 Oracle數據庫

OCP證書是什么&#xff1f; OCP&#xff0c;全稱Oracle Certified Professional&#xff0c;是Oracle公司的Oracle數據庫DBA&#xff08;Database Administrator&#xff0c;數據庫管理員)認證課程。這是Oracle公司針對數據庫管理領域設立的一項認證課程&#xff0c;旨在評估和…

C++ 學習筆記(Structured bindings)

C 學習筆記&#xff08;Structured bindings&#xff09; 這個特性是 C17 引入的&#xff0c;個人認為主要是解決如何讓函數返回多個值的問題。在這之前&#xff0c;我們一般用 std::pair 或者 std::tuple 來返回多個值。比如下面的例子&#xff1a; std::tuple<int, int …

網盤拉新項目去哪找平臺對接?推薦6個一手渠道接單!

在當今這個充滿競爭的時代&#xff0c;網盤項目的尋找與對接成為了許多團隊關注的焦點。那么&#xff0c;我們應該如何找到那些既靠譜又有潛力的項目呢&#xff1f;經過深入研究和全網檢索&#xff0c;我為大家盤點了6個值得一試的接單渠道&#xff0c;助力網盤推廣團隊高效尋找…