为了账号安全,请及时绑定邮箱和手机立即绑定

实现使用Langchain的代理型RAG

基于原生的RAG

在原生的 RAG 中,用户会被输入到 RAG 管道中,该管道会执行检索、重排序、合成并生成响应。

什么是代理检索增强(Agentic RAG)?

代理式RAG是一种基于代理的方法,用于以协调的方式在多个文档上执行问答。可以比较不同的文档,总结特定的文档或比较多个摘要。代理式RAG是一种灵活的问答方法和框架。

在这里,我们本质上是使用代理而不是直接使用大语言模型来完成一系列任务,这些任务需要规划、多步推理、工具使用以及/或者随着时间的推移进行学习。

来源

基本架构

基本架构是为每个文档设置一个 文档代理,每个 文档代理 都能够在其自身的文档内执行问答和摘要功能。

然后设置一个顶层代理(元代理)来管理所有较低级别的文档代理。

来源 : Llama-index

使用的技术栈
  • Langchain — 更具体地说是 LCEL:用于开发 LLM 应用的编排框架
  • OpenAI — LLM
  • FAISS-cpu — 向量存储库
数据源

在这里我们将利用 ArxivLoader 来检索发表在 arXiv 上的文章的元数据。

代码实现

安装所需的依赖

    !pip install -qU langchain langchain_openai langgraph arxiv duckduckgo-search  
    !pip install -qU faiss-cpu pymupdf

设置环境变量

    from google.colab import userdata  
    from uuid import uuid4  
    import os  
    #  
    os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')  
    os.environ["LANGCHAIN_TRACING_V2"] = "true"  
    os.environ["LANGCHAIN_PROJECT"] = f"AIE1 - LangGraph - {uuid4().hex[0:8]}"  
    os.environ["LANGCHAIN_API_KEY"] = userdata.get('LANGCHAIN_API_KEY')
from langchain.text_splitter import RecursiveCharacterTextSplitter  
from langchain_community.document_loaders import ArxivLoader  
from langchain_community.vectorstores import FAISS  
from langchain_openai import OpenAIEmbeddings  

# 加载与特定主题相关的文档  
docs = ArxivLoader(query="Retrieval Augmented Generation", load_max_docs=5).load()  

# 将文档拆分成更小的片段  
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(  
    chunk_size=350, chunk_overlap=50  
)  

chunked_documents = text_splitter.split_documents(docs)  

# 实例化嵌入模型  
embeddings = OpenAIEmbeddings(model="text-embedding-3-small",openai_api_key=os.environ['OPENAI_API_KEY'])  

# 创建索引- 将文档片段加载到向量存储中  
faiss_vectorstore = FAISS.from_documents(  
    documents=chunked_documents,  
    embedding=embeddings,  
)  

# 创建检索器  
retriever = faiss_vectorstore.as_retriever()

生成RAG提示

    from langchain_core.prompts import ChatPromptTemplate  

    RAG_PROMPT = """\  
    使用以下上下文来回答用户的查询。如果无法回答问题,请回复 '我不知道'。  

    问题:  
    {question}  

    上下文:  
    {context}  
    """  

    rag_prompt = ChatPromptTemplate.from_template(RAG_PROMPT)

实例化LLM

    from langchain_openai import ChatOpenAI  

    openai_chat_model = ChatOpenAI(model="gpt-3.5-turbo")

构建 LCEL RAG 链

    from operator import itemgetter  
    from langchain.schema.output_parser import StrOutputParser  
    from langchain.schema.runnable import RunnablePassthrough  

    retrieval_augmented_generation_chain = (  
           {"context": itemgetter("question")   
        | retriever, "question": itemgetter("question")}  
        | RunnablePassthrough.assign(context=itemgetter("context"))  
        | {"response": rag_prompt | openai_chat_model, "context": itemgetter("context")}  
    )  
    #  
    retrieval_augmented_generation_chain  

    ################### 响应 ###############################  

      context: RunnableLambda(itemgetter('question'))  
               | VectorStoreRetriever(tags=['FAISS', 'OpenAIEmbeddings'], vectorstore=<langchain_community.vectorstores.faiss.FAISS object at 0x7de64bd18f40>),  
      question: RunnableLambda(itemgetter('question'))  
    }  
    | RunnableAssign(mapper={  
        context: RunnableLambda(itemgetter('context'))  
      })  
    | {  
        response: ChatPromptTemplate(input_variables=['context', 'question'], messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['context', 'question'], template="使用以下上下文回答用户的查询。如果您无法回答问题,请回答 '我不知道'。\n\n问题:\n{question}\n\n上下文:\n{context}\n"))])  
                  | ChatOpenAI(client=<openai.resources.chat.completions.Completions object at 0x7de64d033850>, async_client=<openai.resources.chat.completions.AsyncCompletions object at 0x7de64d032b60>, openai_api_key=SecretStr('**********'), openai_proxy=''),  
        context: RunnableLambda(itemgetter('context'))  
      }

询问查询

    await retrieval_augmented_generation_chain.ainvoke({"question" : "什么是检索增强生成?"})  

    ##################### 回复 #############################  
    {'response': AIMessage(content='检索增强生成是一种结合了深度学习技术和传统检索技术的文本生成范式。它通过以即插即用的方式显式获取知识,在许多NLP任务中实现了最先进的性能,从而提高了可扩展性并可能减轻了文本生成的难度。它涉及从检索到的人类编写参考文献中生成文本,而不是从头开始生成。', response_metadata={'token_usage': {'completion_tokens': 73, 'prompt_tokens': 2186, 'total_tokens': 2259}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None}),  
     'context': [Document(page_content='grating translation memory to NMT models (Gu\net al., 2018; Zhang et al., 2018; Xu et al., 2020;\nHe et al., 2021). We also review the applications\nof retrieval-augmented generation in other genera-\ntion tasks such as abstractive summarization (Peng\net al., 2019), code generation (Hashimoto et al.,\n2018), paraphrase (Kazemnejad et al., 2020; Su\net al., 2021b), and knowledge-intensive generation\n(Lewis et al., 2020b). Finally, we also point out\nsome promising directions on retrieval-augmented\ngeneration to push forward the future research.\n2\nRetrieval-Augmented Paradigm\nIn this section, we first give a general formulation\nof retrieval-augmented text generation. Then, we\ndiscuss three major components of the retrieval-\naugmented generation paradigm, including the re-\narXiv:2202.01110v2  [cs.CL] 13 Feb 2022\nInput\nSources \n(Sec. 2.2):\nTraining \nCorpus\nExternal Data\nUnsupervised \nData\nMetrics\n(Sec. 2.3):\nSparse-vector \nRetrieval\nDense-vector \nRetrieval\nTask-specific \nRetrieval\nRetrieval Memory\nGeneration Model\nSec. 4: Machine \nTranslation\nSec. 5: Other \nTasks\nData \nAugmentation\nAttention \nMechanism\nSkeleton & \nTemplates\nInformation Retrieval', metadata={'Published': '2022-02-13', 'Title': 'A Survey on Retrieval-Augmented Text Generation', 'Authors': 'Huayang Li, Yixuan Su, Deng Cai, Yan Wang, Lemao Liu', 'Summary': '最近,检索增强文本生成吸引了计算语言学社区越来越多的关注。与传统的生成模型相比,检索增强文本生成具有显著的优势,并在许多NLP任务中实现了最先进的性能。本文旨在对检索增强文本生成进行综述。首先,本文强调了检索增强生成的一般范式,然后根据不同的任务回顾了值得注意的方法,包括对话响应生成、机器翻译和其他生成任务。最后,本文指出了最近方法的一些重要方向,以促进未来的研究。'}),  
      Document(page_content='augmented generation as well as three key com-\nponents under this paradigm, which are retrieval\nsources, retrieval metrics and generation models.\nThen, we introduce notable methods about\nretrieval-augmented generation, which are orga-\nnized with respect to different tasks. Specifically,\non the dialogue response generation task, exem-\nplar/template retrieval as an intermediate step has\nbeen shown beneficial to informative response gen-\neration (Weston et al., 2018; Wu et al., 2019; Cai\net al., 2019a,b). In addition, there has been growing\ninterest in knowledge-grounded generation explor-\ning different forms of knowledge such as knowl-\nedge bases and external documents (Dinan et al.,\n2018; Zhou et al., 2018; Lian et al., 2019; Li et al.,\n2019; Qin et al., 2019; Wu et al., 2021; Zhang et al.,\n2021). On the machine translation task, we summa-\nrize the early work on how the retrieved sentences\n(called translation memory) are used to improve\nstatistical machine translation (SMT) (Koehn et al.,\n2003) models (Simard and Isabelle, 2009; Koehn\nand Senellart, 2010) and in particular, we inten-\nsively highlight several popular methods to inte-\ngrating translation memory to NMT models (Gu\net al., 2018; Zhang et al., 2018; Xu et al., 2020;\nHe et al., 2021). We also review the applications', metadata={'Published': '2022-02-13', 'Title': 'A Survey on Retrieval-Augmented Text Generation', 'Authors': 'Huayang Li, Yixuan Su, Deng Cai, Yan Wang, Lemao Liu', 'Summary': '最近,检索增强文本生成吸引了计算语言学社区越来越多的关注。与传统的生成模型相比,检索增强文本生成具有显著的优势,并在许多NLP任务中实现了最先进的性能。本文旨在对检索增强文本生成进行综述。首先,本文强调了检索增强生成的一般范式,然后根据不同的任务回顾了值得注意的方法,包括对话响应生成、机器翻译和其他生成任务。最后,本文指出了最近方法的一些重要方向,以促进未来的研究。'}),  
      Document(page_content='recent methods to facilitate future research.\n1\nIntroduction\nRetrieval-augmented text generation, as a new\ntext generation paradigm that fuses emerging deep\nlearning technology and traditional retrieval tech-\nnology, has achieved state-of-the-art (SOTA) per-\nformance in many NLP tasks and attracted the at-\ntention of the computational linguistics community\n(Weston et al., 2018; Dinan et al., 2018; Cai et al.,\n2021). Compared with generation-based counter-\npart, this new paradigm has some remarkable ad-\nvantages: 1) The knowledge is not necessary to be\nimplicitly stored in model parameters, but is explic-\nitly acquired in a plug-and-play manner, leading\nto great scalibility; 2) Instead of generating from\nscratch, the paradigm generating text from some re-\ntrieved human-written reference, which potentially\nalleviates the difficulty of text generation.\nThis paper aims to review many representative\napproaches for retrieval-augmented text generation\ntasks including dialogue response generation (We-\nston et al., 2018), machine translation (Gu et al.,\n2018) and others (Hashimoto et al., 2018). We\n∗All authors contributed equally.\nfirstly present the generic paradigm of retrieval-\naugmented generation as well as three key com-\nponents under this paradigm, which are retrieval\nsources, retrieval metrics and generation models.\nThen, we introduce notable methods about', metadata={'Published': '2022-02-13', 'Title': 'A Survey on Retrieval-Augmented Text Generation', 'Authors': 'Huayang Li, Yixuan Su, Deng Cai, Yan Wang, Lemao Liu', 'Summary': '最近,检索增强文本生成吸引了计算语言学社区越来越多的关注。与传统的生成模型相比,检索增强文本生成具有显著的优势,并在许多NLP任务中实现了最先进的性能。本文旨在对检索增强文本生成进行综述。首先,本文强调了检索增强生成的一般范式,然后根据不同的任务回顾了值得注意的方法,包括对话响应生成、机器翻译和其他生成任务。最后,本文指出了最近方法的一些重要方向,以促进未来的研究。'}),  
      Document(page_content='A Survey on Retrieval-Augmented Text Generation\nHuayang Li♥,∗\nYixuan Su♠,∗\nDeng Cai♦,∗\nYan Wang♣,∗\nLemao Liu♣,∗\n♥Nara Institute of Science and Technology\n♠University of Cambridge\n♦The Chinese University of Hong Kong\n♣Tencent AI Lab\nli.huayang.lh6@is.naist.jp, ys484@cam.ac.uk\nthisisjcykcd@gmail.com, brandenwang@tencent.com\nlemaoliu@gmail.com\nAbstract\nRecently, retrieval-augmented text generation\nattracted increasing attention of the compu-\ntational linguistics community.\nCompared\nwith conventional generation models, retrieval-\naugmented text generation has remarkable ad-\nvantages and particularly has achieved state-of-\nthe-art performance in many NLP tasks. This\npaper aims to conduct a survey about retrieval-\naugmented text generation. It firstly highlights\nthe generic paradigm of retrieval-augmented\ngeneration, and then it reviews notable ap-\nproaches according to different tasks including\ndialogue response generation, machine trans-\nlation, and other generation tasks. Finally, it\npoints out some promising directions on top of\nrecent methods to facilitate future research.\n1\nIntroduction\nRetrieval-augmented text generation, as a new\ntext generation paradigm that fuses emerging deep\nlearning technology and traditional retrieval tech-', metadata={'Published': '2022-02-13', 'Title': 'A Survey on Retrieval-Augmented Text Generation', 'Authors': 'Huayang Li, Yixuan Su, Deng Cai, Yan Wang, Lemao Liu', 'Summary': '最近,检索增强文本生成吸引了计算语言学社区越来越多的关注。与传统的生成模型相比,检索增强文本生成具有显著的优势,并在许多NLP任务中实现了最先进的性能。本文旨在对检索增强文本生成进行综述。首先,本文强调了检索增强生成的一般范式,然后根据不同的任务回顾了值得注意的方法,包括对话响应生成、机器翻译和其他生成任务。最后,本文指出了最近方法的一些重要方向,以促进未来的研究。'})]}

创建我们的工具带

像通常一样,我们希望为代理配备一个工具箱,以帮助回答问题并添加外部知识。

LangChain 社区仓库 中有很多工具,但我们只使用其中几个,以便观察 LangGraph 的循环特性!

在这里我们将利用:

- Duck Duck Go 网页搜索

from langchain_community.tools.ddg_search import DuckDuckGoSearchRun  
from langchain_community.tools.arxiv.tool import ArxivQueryRun  
from langgraph.prebuilt import ToolExecutor  
tool_belt = [  
    DuckDuckGoSearchRun(),  
    ArxivQueryRun()  
]  

tool_executor = ToolExecutor(tool_belt)

实例化 Openai 函数调用

    from langchain_core.utils.function_calling import convert_to_openai_function  
    #  
    model = ChatOpenAI(temperature=0)  
    #  
    functions = [convert_to_openai_function(t) for t in tool_belt]  
    model = model.bind_functions(functions)

利用 LangGraph

LangGraph 使用一个 StatefulGraph,该 StatefulGraph 使用 AgentState 对象在图的各个节点之间传递信息。

有更多选项我们下面不会看到——但这个 AgentState 对象是存储在 TypedDict 中的一个实例,键为 messages,值是一个 BaseMessages 的序列,每当状态发生变化时,该序列会被追加。

from typing import TypedDict, Annotated, Sequence  
import operator  
from langchain_core.messages import BaseMessage  

class AgentState(TypedDict):  
  messages: Annotated[Sequence[BaseMessage], operator.add]

构建节点

  • call_model 是一个节点,将会……好吧……调用模型
  • call_tool 是一个节点,将会调用一个工具
    from langgraph.prebuilt import ToolInvocation  
    import json  
    from langchain_core.messages import FunctionMessage  

    def call_model(state):  
      messages = state["messages"]  
      response = model.invoke(messages)  
      return {"messages" : [response]}  

    def call_tool(state):  
      last_message = state["messages"][-1]  

      action = ToolInvocation(  
          tool=last_message.additional_kwargs["function_call"]["name"],  
          tool_input=json.loads(  
              last_message.additional_kwargs["function_call"]["arguments"]  
          )  
      )  

      response = tool_executor.invoke(action)  

      function_message = FunctionMessage(content=str(response), name=action.tool)  

      return {"messages" : [function_message]}

构建工作流

    from langgraph.graph import StateGraph, END  

    workflow = StateGraph(AgentState)  

    workflow.add_node("agent", call_model)  
    workflow.add_node("action", call_tool)  
    workflow.nodes  
    ########################RESPONSE############################  
    {'agent': RunnableLambda(call_model), 'action': RunnableLambda(call_tool)}

设置入口点

    workflow.set_entry_point("agent")

构建一个条件边用于路由

    def should_continue(state):  
      last_message = state["messages"][-1]  

      if "function_call" not in last_message.additional_kwargs:  
        return "end"  

      return "continue"  

    workflow.add_conditional_edges(  
        "agent",  
        should_continue,  
        {  
            "continue" : "action",  
            "end" : END  
        }  
    )

最后将条件边连接到代理节点和动作节点

    workflow.add_edge("action", "agent")

编译工作流

    app = workflow.compile()  
    #  
    app  
    #  
    ################### 响应 ###############################  
    CompiledGraph(nodes={'agent': ChannelInvoke(bound=RunnableLambda(call_model)  
    | ChannelWrite(channels=[ChannelWriteEntry(channel='agent', value=None, skip_none=False), ChannelWriteEntry(channel='messages', value=RunnableLambda(...), skip_none=False)]), config={'tags': []}, channels={'messages': 'messages'}, triggers=['agent:inbox'], mapper=functools.partial(<function _coerce_state at 0x7de64d4c9ab0>, <class '__main__.AgentState'>)), 'action': ChannelInvoke(bound=RunnableLambda(call_tool)  
    | ChannelWrite(channels=[ChannelWriteEntry(channel='action', value=None, skip_none=False), ChannelWriteEntry(channel='messages', value=RunnableLambda(...), skip_none=False)]), config={'tags': []}, channels={'messages': 'messages'}, triggers=['action:inbox'], mapper=functools.partial(<function _coerce_state at 0x7de64d4c9ab0>, <class '__main__.AgentState'>)), 'agent:edges': ChannelInvoke(bound=RunnableLambda(runnable), config={'tags': ['langsmith:hidden']}, channels={'messages': 'messages'}, triggers=['agent']), 'action:edges': ChannelInvoke(bound=ChannelWrite(channels=[ChannelWriteEntry(channel='agent:inbox', value='action', skip_none=True)]), config={'tags': ['langsmith:hidden']}, channels={'messages': 'messages'}, triggers=['action']), '__start__': ChannelInvoke(bound=ChannelWrite(channels=[ChannelWriteEntry(channel='__start__', value=None, skip_none=False), ChannelWriteEntry(channel='messages', value=RunnableLambda(...), skip_none=False)]), config={'tags': ['langsmith:hidden']}, channels={None: '__start__:inbox'}, triggers=['__start__:inbox']), '__start__:edges': ChannelInvoke(bound=ChannelWrite(channels=[ChannelWriteEntry(channel='agent:inbox', value=None, skip_none=False)]), config={'tags': ['langsmith:hidden']}, channels={'messages': 'messages'}, triggers=['__start__'])}, channels={'messages': <langgraph.channels.binop.BinaryOperatorAggregate object at 0x7de64de19e70>, 'agent:inbox': <langgraph.channels.any_value.AnyValue object at 0x7de64de19fc0>, 'action:inbox': <langgraph.channels.any_value.AnyValue object at 0x7de64de1a080>, '__start__:inbox': <langgraph.channels.any_value.AnyValue object at 0x7de64de1ad10>, 'agent': <langgraph.channels.ephemeral_value.EphemeralValue object at 0x7de64de1b2b0>, 'action': <langgraph.channels.ephemeral_value.EphemeralValue object at 0x7de64de1b880>, '__start__': <langgraph.channels.ephemeral_value.EphemeralValue object at 0x7de64de18340>, '__end__': <langgraph.channels.last_value.LastValue object at 0x7de64de18490>, <ReservedChannels.is_last_step: 'is_last_step'>: <langgraph.channels.last_value.LastValue object at 0x7de64de1b670>}, output='__end__', hidden=['agent:inbox', 'action:inbox', '__start__', 'messages'], snapshot_channels=['messages'], input='__start__:inbox', graph=<langgraph.graph.state.StateGraph object at 0x7de64de18880>)

调用 LangGraph- 查询

    from langchain_core.messages import HumanMessage  

    inputs = {"messages" : [HumanMessage(content="什么是大型语言模型中的RAG?它是什么时候出现的?")]}  

    response = app.invoke(inputs)  
    print(response)  
    ############################响应 #########################  
    {'messages': [HumanMessage(content='什么是大型语言模型中的RAG?它是什么时候出现的?'),  
      AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{"query":"RAG in the context of Large Language Models"}', 'name': 'duckduckgo_search'}}, response_metadata={'token_usage': {'completion_tokens': 25, 'prompt_tokens': 171, 'total_tokens': 196}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'function_call', 'logprobs': None}),  
      FunctionMessage(content="大型语言模型(LLM)在处理和生成文本方面非常强大。然而,它们在理解更广泛的信息上下文方面存在固有的困难,尤其是在处理长对话或复杂任务时。这就是大型上下文窗口和检索增强生成(RAG)发挥作用的地方。这些高级的通用语言模型是在庞大的数据集上训练的,使它们能够理解和生成类似人类的文本。在RAG的背景下,LLM根据用户查询从向量数据库检索的上下文信息生成完整的响应。在语言模型迅速发展的背景下,关于检索增强生成(RAG)和长上下文大型语言模型(LLM)的辩论引起了广泛关注。检索增强生成(RAG)是一种AI框架,通过将模型基于外部知识来源来补充LLM的内部信息表示,从而提高LLM生成响应的质量。在基于LLM的问答系统中实现RAG有两个主要好处:它确保模型具有... RAG代表检索增强生成(RAG)。RAG使大型语言模型(LLM)能够访问和利用最新的信息。因此,它提高了LLM响应的相关性和质量。以下是RAG实现的简单图示。", name='duckduckgo_search'),  
      AIMessage(content="RAG代表大型语言模型(LLM)中的检索增强生成。它是一种AI框架,通过将模型基于外部知识来源来补充LLM的内部信息表示,从而提高LLM生成响应的质量。RAG使LLM能够访问和利用最新的信息,从而提高模型生成的响应的相关性和质量。RAG在语言模型迅速发展的背景下作为增强LLM理解和生成类似人类文本能力的一种方式出现。", response_metadata={'token_usage': {'completion_tokens': 117, 'prompt_tokens': 491, 'total_tokens': 608}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_3bc1b5746c', 'finish_reason': 'stop', 'logprobs': None})]}
    response['messages'][-1].content  

    ####### 响应 #####################  
    RAG代表大型语言模型(LLM)中的检索增强生成。它是一种AI框架,通过将模型基于外部知识来源来补充LLM的内部信息表示,从而提高LLM生成响应的质量。RAG使LLM能够访问和利用最新的信息,从而提高模型生成的响应的相关性和质量。RAG在语言模型迅速发展的背景下作为增强LLM理解和生成类似人类文本能力的一种方式出现。

提问查询

    question = "谁是《基于检索增强的生成》论文的主要作者,他们就读于哪所大学?"  

    inputs = {"messages" : [HumanMessage(content=question)]}  
    #  
    response = app.invoke(inputs)  
    print(response['messages'][-1].content)  
    ################# 响应 ##############################  
    《基于检索增强的生成》论文的主要作者是 Huayang Li。遗憾的是,他们就读的大学在提供的摘要中没有提及。

提问查询

    question = "谁是论文《基于检索增强的文本生成综述》的主要作者?"  

    inputs = {"messages" : [HumanMessage(content=question)]}  

    response = app.invoke(inputs)  
    print(response['messages'][-1].content)  
    ################# 响应 ##############################  
    论文《基于检索增强的文本生成综述》的主要作者是 Huayang Li、Yixuan Su、Deng Cai、Yan Wang 和 Lemao Liu。
参考资料

LlamaIndex V 0.10.15 文档

2024 AI 用户大会:Jerry Liu 主题演讲

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消