• 为什么是LangChain?


    一、前言

    我们知道因为一些不得已的原因,一些国外比较优秀的技术,我们不能看到,比如,如果我们想要借助 OpenAI 或 Hugging Face 创建基于大语言模型的应用程序。若非一些特殊方法,是难以实现的。从cahtgpt发布到今天不过一年时间,市面上的LLM已经百花齐放,不得不感慨技术革命的速度,为了能轻松构建大语言模型应用。不得不提起一个非常强大的第三方开源库:LangChain 。
    官方文档
    这个库目前非常活跃,已经67Kstart了,每天都在迭代,更新速度飞快。

    LangChain 是一个我们与大模型互动的一个桥梁。他主要拥有 2 个能力:

    1. 作为大模型与本地数据的一个器哦啊两联系起来,也就是将 LLM 模型与外部数据源进行连接
    2. 让开发者与 LLM 模型进行更加友好的交互

    而且:LangChain 是一个旨在帮助您轻松构建大语言模型应用的框架,它可以帮助我们:

    1. 为各种不同基础模型提供统一接口(参见Models)
    2. 帮助管理提示的框架(参见Prompts),能够实现,多个prompt 配合使用。
    3. 一套中心化接口,用于处理长期记忆(Memory)、外部数据(Indexes)、其他 LLM(Chains)以及 LLM 无法处理的任务的其他代理(例如,计算或搜索)。
      LLM 模型:Large Language Model,大型语言模型

    LangChain是一个用于开发由语言模型驱动的应用程序的框架。它使应用程序能够:
    具有上下文意识:将语言模型与上下文源(提示指令,少量示例,基于其响应的内容等)联系起来。
    推理:依靠语言模型进行推理(关于如何根据提供的上下文进行回答,采取什么行动等)。
    朗链的主要价值支柱有:
    组件:用于处理语言模型的抽象,以及每个抽象的实现集合。组件是模块化的,易于使用,无论你

    二、认识langchain

    1. langchain的主要组成

    LangChain 6大核心模块

    Models:从不同的 LLM 和嵌入模型中进行选择
    Prompts:管理 LLM 输入
    Chains:将 LLM 与其他组件相结合
    Indexes:访问外部数据
    Memory:记住以前的对话
    Agents:访问其他工具

    2. 总览LangChain

    langchain是个优雅的框架。

    Models:从不同的 LLM 和嵌入模型中进行选择
    支持多种模型接口,比如 OpenAI、Hugging Face、AzureOpenAI …
    Fake LLM,用于测试
    缓存的支持,比如 in-mem(内存)、SQLite、Redis、SQL用量记录
    支持流模式(就是一个字一个字的返回,类似打字效果)

    Prompts:管理 LLM 输入
    Prompt管理,支持各种自定义模板
    拥有大量的文档加载器,比如 Email、Markdown、PDF、Youtube …

    Indexes:访问外部数据
    对索引的支持
    文档分割器
    向量化
    对接向量存储与搜索,比如 Chroma、Pinecone、Qdrand

    Chains:将 LLM 与其他组件相结合
    LLMChain
    各种工具Chain
    LangChainHub

    2. LangChain的六大核心模块

    1. Models:模型统一接口

    各种类型的模型和模型集成,比如OpenAI的各个API/GPT-4等等,为各种不同基础模型提供统一接口,也就是说在调用模型的时候,我们可以只通过 一个入口:
    比如:这里以 ChatGPT为例,因为特殊原因,api的key,秘钥这些东西 需要申请。

    import os
    os.environ["OPENAI_API_KEY"] = '你的api key'
    from langchain.llms import OpenAI
     
    llm = OpenAI(model_name="text-davinci-003",max_tokens=1024)
    llm("啥是人工智能")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    # 可以选择的模型。以及模型的最大输入 token
            model_token_mapping = {
                "gpt-4": 8192,
                "gpt-4-0314": 8192,
                "gpt-4-0613": 8192,
                "gpt-4-32k": 32768,
                "gpt-4-32k-0314": 32768,
                "gpt-4-32k-0613": 32768,
                "gpt-3.5-turbo": 4096,
                "gpt-3.5-turbo-0301": 4096,
                "gpt-3.5-turbo-0613": 4096,
                "gpt-3.5-turbo-16k": 16385,
                "gpt-3.5-turbo-16k-0613": 16385,
                "text-ada-001": 2049,
                "ada": 2049,
                "text-babbage-001": 2040,
                "babbage": 2049,
                "text-curie-001": 2049,
                "curie": 2049,
                "davinci": 2049,
                "text-davinci-003": 4097,
                "text-davinci-002": 4097,
                "code-davinci-002": 8001,
                "code-davinci-001": 8001,
                "code-cushman-002": 2048,
                "code-cushman-001": 2048,
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2. Prompts:管理 LLM 输入

    from langchain import PromptTemplate, FewShotPromptTemplate
    
    examples = [
        {
       "word": "高兴", "antonym": "悲伤"},
        {
       "word": "高大", "antonym": "矮小"},
    ]
    
    example_template = 
    """
    词语: {word}
    反义词: {antonym}\n
    """
    
    example_prompt = PromptTemplate(
        input_variables=["word", "antonym"],
        template=example_template,
    )
    
    few_shot_prompt = FewShotPromptTemplate(
        examples=examples,
        example_prompt=example_prompt,
        prefix="给出输入词语的反义词",
        suffix="词语: {input}\n反义词:",
        input_variables=["input"],
        example_separator="\n",
    )
    
    few_shot_prompt.format(input="美丽")
    #上面的代码将生成一个提示模板,并根据提供的示例和输入组成以下提示:
    
    #给出输入词语的反义词
    
    #词语: 高兴
    #反义词: 悲伤
    
    
    #词语: 高大
    #反义词: 矮小
    
    #词语: 美丽
    #反义词:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    3. Chains:将 LLM 与其他组件相结合,执行多个chain

    from langchain.llms import OpenAI
    from langchain.chains import LLMChain
    from langchain.prompts import PromptTemplate
    from langchain.chains import SimpleSequentialChain
    
    # location 链
    llm = OpenAI(temperature=1)
    template = """Your job is to come up with a classic dish from the area that the users suggests.
    % USER LOCATION
    {user_location}
    
    YOUR RESPONSE:
    """
    prompt_template = PromptTemplate(input_variables=["user_location"], template=template)
    location_chain = LLMChain(llm=llm, prompt=prompt_template)
    
    # meal 链
    template = """Given a meal, give a short and simple recipe on how to make that dish at home.
    % MEAL
    {user_meal}
    
    YOUR RESPONSE:
    """
    prompt_template = PromptTemplate(input_variables=["user_meal"], template=template)
    meal_chain = LLMChain(llm=llm, prompt=prompt_template)
    
    # 通过 SimpleSequentialChain 串联起来,第一个答案会被替换第二个中的user_meal,然后再进行询问
    overall_chain = SimpleSequentialChain(chains=[location_chain, meal_chain], verbose=True)
    review = overall_chain.run("Rome")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    4. Indexes:访问外部数据

    访问外部数据,不得不介绍几个额外的,功能,就是langchain自带的几个模块。配合这几个小模块实现,外部数据的访问

    a. Loader 加载器

    这个就是从指定源进行加载数据的。比如:文件夹 DirectoryLoader、Azure 存储 AzureBlobStorageContainerLoader、CSV文件 CSVLoader、印象笔记 EverNoteLoader、Google网盘 GoogleDriveLoader、任意的网页 UnstructuredHTMLLoader、PDF PyPDFLoader、S3 S3DirectoryLoader/S3FileLoader、Youtube YoutubeLoader 等等,上面只是简单的进行列举了几个,官方提供了超级的多的加载器供你使用。
    关于这里的官方介绍

    #示例:
    from langchain.document_loaders import UnstructuredFileLoader
    from langchain.chains.summarize import load_summarize_chain
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    from langchain import OpenAI
    
    # 导入文本
    loader = UnstructuredFileLoader("/content/sample_data/data/lg_test.txt")
    # 将文本转成 Document 对象
    document = loader.load()
    print(f'documents:{len(document)}')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    b. Document 文档(解决加载数据超过模型最大输入) 与 Text Spltters 文本分割

    当使用loader加载器读取到数据源后,数据源需要转换成 Document 对象后,后续才能进行使用。
    Text Spltters ,文本分割就是用来分割文本的。为什么需要分割文本?因为我们每次不管是做把文本当作 prompt 发给 openai api ,还是还是使用 openai api embedding 功能都是有字符限制的。

    #示例:
    
    from langchain.document_loaders import UnstructuredFileLoader
    from langchain.chains.summarize import load_summarize_chain
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    from langchain import OpenAI
    
    # 导入文本
    loader = UnstructuredFileLoader("/content/sample_data/data/lg_test.txt")
    # 将文本转成 Document 对象
    document = loader.load()
    print(f'documents:{len(document)}')
    
    # 初始化文本分割器
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size = 500,
        chunk_overlap = 0
    )
    
    # 切分文本
    split_documents = text_splitter.split_documents(document)
    print(f'documents:{len(split_documents)}')
    
    # 加载 llm 模型
    llm = OpenAI(model_name="text-davinci-003", max_tokens=1500)
    
    # 创建总结链
    chain = load_summarize_chain(llm, chain_type="refine", verbose=True)
    
    # 执行总结链,(为了快速演示,只总结前5段)
    chain.run(split_documents[:5])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    这里有几个参数需要注意:

    1. 文本分割器的 chunk_overlap 参数
      这个是指切割后的每个 document 里包含几个上一个 document 结尾的内容,主要作用是为了增加每个 document 的上下文关联。比如,chunk_overlap=0时, 第一个 document 为 aaaaaa,第二个为 bbbbbb;当 chunk_overlap=2 时,第一个 document 为 aaaaaa,第二个为 aabbbbbb。

    2. chain 的 chain_type 参数

    这个参数主要控制了将 document 传递给 llm 模型的方式,一共有 4 种方式:
    stuff: 这种最简单粗暴,会把所有的 document 一次全部传给 llm 模型进行总结。如果document很多的话,势必会报超出最大 token 限制的错,所以总结文本的时候一般不会选中这个。
    map_reduce: 这个方式会先将每个 document 进行总结,最后将所有 document 总结出的结果再进行一次总结。
    refine: 这种方式会先总结第一个 document,然后在将第一个 document 总结出的内容和第二个 document 一起发给 llm 模型在进行总结,以此类推。这种方式的好处就是在总结后一个 document 的时候,会带着前一个的 document 进行总结,给需要总结的 document 添加了上下文,增加了总结内容的连贯性
    map_rerank: 这种一般不会用在总结的 chain 上,而是会用在问答的 chain 上,他其实是一种搜索答案的匹配方式。首先你要给出一个问题,他会根据问题给每个 document 计算一个这个 document 能回答这个问题的概率分数,然后找到分数最高的那个 document ,在通过把这个 document 转化为问题的 prompt 的一部分(问题+document)发送给 llm 模型,最后 llm 模型返回具体答案。

    d. Vectorstores 向量数据库

    因为数据相关性搜索其实是向量运算。所以,不管我们是使用 openai api embedding 功能还是直接通过向量数据库直接查询,都需要将我们的加载进来的数据 Document 进行向量化,才能进行向量运算搜索。转换成向量也很简单,只需要我们把数据存储到对应的向量数据库中即可完成向量的转换。
    官方也提供了很多的向量数据库供我们使用。

    #示例:
    from langchain.vectorstores import Chroma
    
    # 持久化数据
    docsearch = Chroma.from_documents(documents, embeddings, persist_directory="D:/vector_store")
    docsearch.persist()
    
    # 加载数据
    docsearch = Chroma(persist_directory="D:/vector_store", embedding_function=embeddings)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    e. Embedding

    用于衡量文本的相关性。这个也是 OpenAI API 能实现构建自己知识库的关键所在。
    他相比 fine-tuning 最大的优势就是,不用进行训练,并且可以实时添加新的内容,而不用加一次新的内容就训练一次,并且各方面成本要比 fine-tuning 低很多。

    5. Memory:记住以前的对话

    使用Memory实现一个带记忆的对话机器人

    from langchain.memory import ChatMessageHistory
    from langchain.chat_models import ChatOpenAI
    
    chat = ChatOpenAI(temperature=0)
    
    # 初始化 MessageHistory 对象
    history = ChatMessageHistory()
    
    # 给 MessageHistory 对象添加对话内容
    history.add_ai_message("你好!")
    history.add_user_message("中国的首都是哪里?")
    
    # 执行对话
    ai_response = chat(history.messages)
    print(ai_response)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    6. Agents:访问其他工具,自定义agent中所使用的工具

    自定义agent中所使用的工具

    from langchain.agents import initialize_agent, Tool
    from langchain.agents import AgentType
    from langchain.tools import BaseTool
    from langchain.llms import OpenAI
    from langchain import LLMMathChain, SerpAPIWrapper
    
    llm = OpenAI(temperature=0)
    
    # 初始化搜索链和计算链
    search = SerpAPIWrapper()
    llm_math_chain = LLMMathChain(llm=llm, verbose=True)
    
    # 创建一个功能列表,指明这个 agent 里面都有哪些可用工具,agent 执行过程可以看必知概念里的 Agent 那张图
    tools = [
        Tool(
            name = "Search",
            func=search.run,
            description="useful for when you need to answer questions about current events"
        ),
        Tool(
            name="Calculator",
            func=llm_math_chain.run,
            description="useful for when you need to answer questions about math"
        )
    ]
    
    # 初始化 agent
    agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
    
    # 执行 agent
    agent.run("Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    三、经典示例

    1. 最简单的交互

    用 LangChain 加载 OpenAI 的模型,并且完成一次问答。我们需要先设置我们的 openai 的 key,这个 key 可以在用户管理里面创建。

    import os
    os.environ["OPENAI_API_KEY"] = '你的api key'
    然后,我们进行导入和执行
    from langchain.llms import OpenAI
    
    llm = OpenAI(model_name="text-davinci-003",max_tokens=1024)
    llm("怎么评价人工智能")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2. 构建本地知识库问答机器人

    从本地读取多个文档构建知识库,并且使用 Openai API 在知识库中进行搜索并给出答案。

    from langchain.embeddings.openai import OpenAIEmbeddings
    from langchain.vectorstores import Chroma
    from langchain.text_splitter import CharacterTextSplitter
    from langchain import OpenAI
    from langchain.document_loaders import DirectoryLoader
    from langchain.chains import RetrievalQA
    
    # 加载文件夹中的所有txt类型的文件
    loader = DirectoryLoader('/content/sample_data/data/', glob='**/*.txt')
    # 将数据转成 document 对象,每个文件会作为一个 document
    documents = loader.load()
    
    # 初始化加载器
    text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0)
    # 切割加载的 document
    split_docs = text_splitter.split_documents(documents)
    
    # 初始化 openai 的 embeddings 对象
    embeddings = OpenAIEmbeddings()
    # 将 document 通过 openai 的 embeddings 对象计算 embedding 向量信息并临时存入 Chroma 向量数据库,用于后续匹配查询
    docsearch = Chroma.from_documents(split_docs, embeddings)
    
    # 创建问答对象
    qa = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=docsearch.as_retriever(), return_source_documents=True)
    # 进行问答
    result = qa({"query": "科大讯飞今年第一季度收入是多少?"})
    print(result)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    3. 让输出内容 结构化起来

    from langchain.output_parsers import StructuredOutputParser, ResponseSchema
    from langchain.prompts import PromptTemplate
    from langchain.llms import OpenAI
    
    llm = OpenAI(model_name="text-davinci-003")
    
    # 告诉他我们生成的内容需要哪些字段,每个字段类型式啥
    response_schemas = [
        ResponseSchema(name="bad_string", description="This a poorly formatted user input string"),
        ResponseSchema(name="good_string", description="This is your response, a reformatted response")
    ]
    
    # 初始化解析器
    output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
    
    # 生成的格式提示符
    # {
    #	"bad_string": string  // This a poorly formatted user input string
    #	"good_string": string  // This is your response, a reformatted response
    #}
    format_instructions = output_parser.get_format_instructions()
    
    template = """
    You will be given a poorly formatted string from a user.
    Reformat it and make sure all the words are spelled correctly
    
    {format_instructions}
    
    % USER INPUT:
    {user_input}
    
    YOUR RESPONSE:
    """
    
    # 将我们的格式描述嵌入到 prompt 中去,告诉 llm 我们需要他输出什么样格式的内容
    prompt = PromptTemplate(
        input_variables=["user_input"],
        partial_variables={"format_instructions": format_instructions},
        template=template
    )
    
    promptValue = prompt.format(user_input="welcom to califonya!")
    llm_output = llm(promptValue)
    
    # 使用解析器进行解析生成的内容
    output_parser.parse(llm_output)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    4. 使用 Hugging Face 模型

    #使用 Hugging Face 模型之前,需要先设置环境变量
    import os
    os.environ['HUGGINGFACEHUB_API_TOKEN'] = ''
    
    • 1
    • 2
    • 3
    1. 使用在线的 Hugging Face 模型
    from langchain import PromptTemplate, HuggingFaceHub, LLMChain
    
    template = """Question: {question}
    Answer: Let's think step by step."""
    
    prompt = PromptTemplate(template=template, input_variables=["question"])
    llm = HuggingFaceHub(repo_id="google/flan-t5-xl", model_kwargs={"temperature":0, "max_length":64})
    llm_chain = LLMChain(prompt=prompt, llm=llm)
    
    question = "What NFL team won the Super Bowl in the year Justin Beiber was born?"
    print(llm_chain.run(question))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 将 Hugging Face 模型直接拉到本地使用
    from langchain import PromptTemplate, LLMChain
    from langchain.llms import HuggingFacePipeline
    from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, AutoModelForSeq2SeqLM
    
    model_id = 'google/flan-t5-large'
    tokenizer = AutoTokenizer.from_pretrained(model_id)
    model = AutoModelForSeq2SeqLM.from_pretrained(model_id)
    
    pipe = pipeline(
        "text2text-generation",
        model=model,
        tokenizer=tokenizer,
        max_length=100
    )
    
    local_llm = HuggingFacePipeline(pipeline=pipe)
    print(local_llm('What is the capital of France? '))
    
    
    template = """Question: {question} Answer: Let's think step by step."""
    prompt = PromptTemplate(template=template, input_variables=["question"])
    
    llm_chain = LLMChain(prompt=prompt, llm=local_llm)
    question = "What is the capital of England?"
    print(llm_chain.run(question))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    将模型拉到本地使用的好处:
    训练模型
    可以使用本地的 GPU
    有些模型无法在 Hugging Face 运行

    4. 通过自然语言执行SQL命令

    我们通过 SQLDatabaseToolkit 或者 SQLDatabaseChain 都可以实现执行SQL命令的操作

    from langchain.agents import create_sql_agent
    from langchain.agents.agent_toolkits import SQLDatabaseToolkit
    from langchain.sql_database import SQLDatabase
    from langchain.llms.openai import OpenAI
    
    db = SQLDatabase.from_uri("sqlite:///../notebooks/Chinook.db")
    toolkit = SQLDatabaseToolkit(db=db)
    
    agent_executor = create_sql_agent(
        llm=OpenAI(temperature=0),
        toolkit=toolkit,
        verbose=True
    )
    
    agent_executor.run("Describe the playlisttrack table")
    from langchain import OpenAI, SQLDatabase, SQLDatabaseChain
    
    db = SQLDatabase.from_uri("mysql+pymysql://root:root@127.0.0.1/chinook")
    llm = OpenAI(temperature=0)
    
    db_chain = SQLDatabaseChain(llm=llm, database=db, verbose=True)
    db_chain.run("How many employees are there?")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    参考文献
    [1].https://blog.csdn.net/lht0909/article/details/130412875
    [2].https://blog.csdn.net/v_JULY_v/article/details/131552592
    [3].https://developer.aliyun.com/article/1221923
    [4].https://liaokong.gitbook.io/llm-kai-fa-jiao-cheng/#jie-gou-hua-shu-chu

  • 相关阅读:
    【可扩展性】谷歌可扩展和弹性应用的模式
    羧基化稀土荧光微球/稀土掺杂二氧化硅荧光微球/水性稀土配合物复合微球荧光油墨应用
    ISP Tuning
    1.1 安装配置CentOS
    JavaScript学习--Day04
    【数论】莫比乌斯反演
    基础SQL 函数
    Servlet详解
    PHP代码审计1—PHP.ini的那些事
    Hadoop源码编译打包
  • 原文地址:https://blog.csdn.net/shdabai/article/details/134161026