限制文档检索链上的上下文令牌

问题描述 投票:0回答:1

代码

import os
from qdrant_client import QdrantClient

from langchain.vectorstores import Qdrant
from langchain.chat_models import ChatOpenAI
from langchain.embeddings import OpenAIEmbeddings
from langchain.chains import create_retrieval_chain
from langchain.schema import HumanMessage, SystemMessage, AIMessage
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

from langchain_core.runnables import RunnablePassthrough

from langchain.callbacks import get_openai_callback

class ConversationChatManagement():

    def _get_existing_vector_store(self, collection_identity):
        qdrant_host = os.environ.get('QDRANT_HOST', 'qdrant-host-set')
        new_client = QdrantClient(host=qdrant_host, port=6333)

        embeddings=OpenAIEmbeddings()
        return Qdrant(
            client=new_client,
            collection_name=collection_identity,
            embeddings=embeddings,
        )

    def _create_prompt_template(self):
        SYSTEM_TEMPLATE = """
        Answer the user's questions based on the below context and message history.
        If the context doesn't contain any relevant information to the question, don't make something up and just say "I don't know":

        <context>
        {context}
        </context>
        """

        messages = [
            (
                "system",
                SYSTEM_TEMPLATE,
            ),
            MessagesPlaceholder(variable_name="messages")
        ]

        return ChatPromptTemplate.from_messages(messages)

    def chat_conversation(self, message_content: str, collection_identity, target_conversation, llm_client: ChatOpenAI):
        # Basically, a list of `HumanMessage, SystemMessage, AIMessage` objects.
        # Representing the conversation history fetched from database..
        history_messages = self._fetch_conversation_history(target_conversation)

        prompt_template = self._create_prompt_template()
        document_chain = create_stuff_documents_chain(llm_client, prompt_template)

        def parse_retriever_input(params: Dict):
            return params["messages"][-1].content

        # Add vector store into document chain to become retrieval chain.
        existing_vector_store = self._get_existing_vector_store(collection_identity)
        retriever = existing_vector_store.as_retriever()
        retrieval_chain = RunnablePassthrough.assign(
            context=parse_retriever_input | retriever,
        ).assign(
            answer=document_chain,
        )

        docs = retriever.invoke(message_content)

        messages = history_messages + [HumanMessage(content=message_content)] 
        message_stream = retrieval_chain.stream({
            "context": docs,
            "messages": messages
        })

代码主要参考自here


问题

对于大型文档,它将返回有关最大上下文长度的错误:

openai.BadRequestError: Error code: 400 - {'error': {'message': "This model's maximum context length is 8192 tokens. However, your messages resulted in 8367 tokens. Please reduce the length of the messages.", 'type': 'invalid_request_error', 'param': 'messages', 'code': 'context_length_exceeded'}}

我正在使用

langchain===0.1.9
openai===1.5.0
langchain-community===0.0.24

目前,升级或降级不是一个可行的选择,因为这可能会导致项目中出现太多重大更改,而这些更改可能无法及时解决。


探索的解决方案

1.
ReduceDocumentsChain

我看到的最好的参考是

ReduceDocumentsChain
的示例(标题中的链接)

但是,问题是示例代码使用了大量已弃用的、不起作用的代码,这些代码在我正在使用的版本中不起作用:

  • OpenAI
    已在
    0.0.10
    中弃用。当我尝试使用我的版本的示例时,这会导致代码库损坏。
  • LLMChain
    已在
    0.1.17
    中弃用;尝试用
    OpenAI
    替换
    ChatOpenAI
    的输入对象会导致其损坏。

诚然,即使这个示例适用于我的版本,我也对如何将消息历史记录附加到其中感到困惑......

2.
ConversationalRetrievalChain

包含一个我认为我可以使用的

max_tokens_limit
。但它已被弃用,并且需要
question_generator: LLMChain
,这也已被弃用... (回想一下
LLMChain
不适用于
ChatOpenAI

正如文档所建议的,

create_history_aware_retriever
create_retrieval_chain
似乎没有提供任何类型的功能/参数来限制令牌长度。


诚然,我是

LangChain
API 的新手。
因此,我怀疑也可能是设计问题...(错误的链/工具,或错误的提示等)
但我无法清楚地识别问题/解决方案是什么。

python langchain py-langchain
1个回答
0
投票

显然就我而言,问题在于我如何将文档/文本插入 Qdrant 商店:

doc = [
    Document(
        page_content=file_contents # This was the ENTIRE document worth of content in string, not good lol
    )
]

vector_ids = qdrant_collection.add_documents(doc)

我将整个文档直接插入到 Qdrant 矢量存储中,而没有将其拆分。
这导致检索文档搜索包含整个文档,这很容易超出令牌限制。


我通过使用 LangChain 的

CharacterTextSplitter
来对文本进行分块来修复它,然后再将其存储到 Qdrant 矢量存储中。

def _get_chunked_text(text):
    text_splitter = CharacterTextSplitter(
        separator='\n',
        chunk_size=1000,
        chunk_overlap=150,
        length_function=len
    )
    return text_splitter.split_text(text)

chunked_text = _get_chunked_text(file_contents)
vector_ids = qdrant_collection.add_texts(chunked_text)

虽然这个“解决方案”对我来说实际上并没有回答尝试限制上下文窗口上的令牌大小的原始问题。
因此我不会将我的解决方案标记为正确的解决方案,并保持这个问题开放......

© www.soinside.com 2019 - 2024. All rights reserved.