moyangzhan / langchain4j-aideepin

基于AI的企业内部提效工具 | Retrieval enhancement generation(RAG) project
http://www.aideepin.com
MIT License
294 stars 79 forks source link

知识库提问报错 java.lang.IllegalArgumentException: text cannot be null #38

Closed Linzy0 closed 1 month ago

Linzy0 commented 1 month ago

我想问一下AI直接提问是正常的,但知识库提问老是报错 java.lang.IllegalArgumentException: text cannot be null ,应该怎么解决呢 image image

moyangzhan commented 1 month ago

@Linzy0 看起来像是AI没有返回内容,建议到千帆的后台看一下对应的请求,确认下千帆是否真的有响应内容后再继续排查代码

Linzy0 commented 1 month ago

@Linzy0 看起来像是AI没有返回内容,建议到千帆的后台看一下对应的请求,确认下千帆是否真的有响应内容后再继续排查代码

目前看是无法索取到向量库的文档,所以没向AI发送请求

moyangzhan commented 1 month ago

因为用了查询压缩,所以跟常见的那种RAG流程不太一样,整个流程大概是这样: 1.获取历史记录-》2.组合用户问题+历史记录=》3.请求LLM进行压缩(普通的http同步请求),以生成新的更适合的用户问题=》4.根据新的用户问题进行文档召回=》5.组合新的用户问题+召回的文档+历史记录=》6.请求LLM进以获取最终答案(流式响应)。 结合截图的错误堆栈,问题出在第3步,查看代码后发现其实是因为LLM返回的内容为空,主要代码可以看下面的3个类:

AiAssistant的代理生成类 DefaultAiServices.java

  Object memoryId = findMemoryId(method, args).orElse(DEFAULT);

  Optional<SystemMessage> systemMessage = prepareSystemMessage(memoryId, method, args);
  UserMessage userMessage = prepareUserMessage(method, args);
  AugmentationResult augmentationResult = null;
  if (context.retrievalAugmentor != null) {
      List<ChatMessage> chatMemory = context.hasChatMemory()
              ? context.chatMemory(memoryId).messages()
              : null;
      Metadata metadata = Metadata.from(userMessage, memoryId, chatMemory);
      AugmentationRequest augmentationRequest = new AugmentationRequest(userMessage, metadata);
      //这里↓↓↓↓↓↓,对用户提问进行增强,里面会对LLM进行一次请求
      augmentationResult = context.retrievalAugmentor.augment(augmentationRequest);
      userMessage = (UserMessage) augmentationResult.chatMessage();
  }

查询压缩 CompressingQueryTransformer.java

    @Override
    public Collection<Query> transform(Query query) {

        List<ChatMessage> chatMemory = query.metadata().chatMemory();
        if (chatMemory.isEmpty()) {
            // no need to compress if there are no previous messages
            return singletonList(query);
        }

        Prompt prompt = createPrompt(query, format(chatMemory));
        //这里↓↓↓↓↓↓,交由LLM进行压缩
        String compressedQueryText = chatLanguageModel.generate(prompt.text());
        Query compressedQuery = query.metadata() == null
                ? Query.from(compressedQueryText)
                : Query.from(compressedQueryText, query.metadata());
        return singletonList(compressedQuery);
    }

千帆的QianfanChatModel.java

       ChatCompletionResponse response = withRetry(() -> client.chatCompletion(param, endpoint).execute(), maxRetries);
       //这里↓↓↓↓↓↓,response.getResult()为null
      return  Response.from(aiMessageFrom(response),
                tokenUsageFrom(response), finishReasonFrom(response.getFinishReason()));

langchain4j的qianfan这一块的代码应该把提示做的更明显一点。 @Linzy0 如果千帆的后台看不到对应的请求,查不到为啥会返回空,要不换成灵积试试?先确定下这个问题是不是因为LLM导致的

Linzy0 commented 1 month ago

因为用了查询压缩,所以跟常见的那种RAG流程不太一样,整个流程大概是这样: 1.获取历史记录-》2.组合用户问题+历史记录=》3.请求LLM进行压缩(普通的http同步请求),以生成新的更适合的用户问题=》4.根据新的用户问题进行文档召回=》5.组合新的用户问题+召回的文档+历史记录=》6.请求LLM进以获取最终答案(流式响应)。 结合截图的错误堆栈,问题出在第3步,查看代码后发现其实是因为LLM返回的内容为空,主要代码可以看下面的3个类:

AiAssistant的代理生成类 DefaultAiServices.java

  Object memoryId = findMemoryId(method, args).orElse(DEFAULT);

  Optional<SystemMessage> systemMessage = prepareSystemMessage(memoryId, method, args);
  UserMessage userMessage = prepareUserMessage(method, args);
  AugmentationResult augmentationResult = null;
  if (context.retrievalAugmentor != null) {
      List<ChatMessage> chatMemory = context.hasChatMemory()
              ? context.chatMemory(memoryId).messages()
              : null;
      Metadata metadata = Metadata.from(userMessage, memoryId, chatMemory);
      AugmentationRequest augmentationRequest = new AugmentationRequest(userMessage, metadata);
      //这里↓↓↓↓↓↓,对用户提问进行增强,里面会对LLM进行一次请求
      augmentationResult = context.retrievalAugmentor.augment(augmentationRequest);
      userMessage = (UserMessage) augmentationResult.chatMessage();
  }

查询压缩 CompressingQueryTransformer.java

    @Override
    public Collection<Query> transform(Query query) {

        List<ChatMessage> chatMemory = query.metadata().chatMemory();
        if (chatMemory.isEmpty()) {
            // no need to compress if there are no previous messages
            return singletonList(query);
        }

        Prompt prompt = createPrompt(query, format(chatMemory));
        //这里↓↓↓↓↓↓,交由LLM进行压缩
        String compressedQueryText = chatLanguageModel.generate(prompt.text());
        Query compressedQuery = query.metadata() == null
                ? Query.from(compressedQueryText)
                : Query.from(compressedQueryText, query.metadata());
        return singletonList(compressedQuery);
    }

千帆的QianfanChatModel.java

       ChatCompletionResponse response = withRetry(() -> client.chatCompletion(param, endpoint).execute(), maxRetries);
       //这里↓↓↓↓↓↓,response.getResult()为null
      return  Response.from(aiMessageFrom(response),
                tokenUsageFrom(response), finishReasonFrom(response.getFinishReason()));

langchain4j的qianfan这一块的代码应该把提示做的更明显一点。 @Linzy0 如果千帆的后台看不到对应的请求,查不到为啥会返回空,要不换成灵积试试?先确定下这个问题是不是因为LLM导致的 @moyangzhan 之前因为设置了RAG参数导致了一直报text cannot be null错误,后来没有设置RAG参数就能对知识库提问,但是提问几次过后后又是出现 java.lang.IllegalArgumentException: text cannot be null的bug,然后我就根据你说的这三段代码进行打断点: image image image

moyangzhan commented 1 month ago

因为设置了RAG参数导致了一直报text cannot be null错误 =》 这些RAG参数发一下呗 @Linzy0

请求时携带历史记录太容易超过LLM的限制了,更不用说请求最终答案时还要加上知识库召回的文档以及后续要加上的知识图谱~~

如果超过限制,就需要对promt的内容做取舍,目前正在处理这个问题~~

在准确设置了LLM的contextWindow(暂定默认5000)前提下,暂定逻辑如下: 1、进行查询压缩时:如果超过LLM的contextWindow,丢弃历史记录,接着计算prompt以判断是继续向LLM请求还是向前端返回提示; 2、向LLM请求最终答案时,请求Prompt的丢弃顺序:a)历史记录 b)召回的文档,然后再根据知识库的设置(是否严格模式)来决定是返回[无答案]还是向LLM继续请求答案

moyangzhan commented 1 month ago

如果用户提问的内容过多,最终向LLM提交的prompt可能不会再携带其他信息(历史记录、召回的文档、知识图谱)

Linzy0 commented 1 month ago

如果用户提问的内容过多,最终向LLM提交的prompt可能不会再携带其他信息(历史记录、召回的文档、知识图谱)

我目前想追求稳定性,所以把AbstractLLMService类中的maxmessages设置为1,减少Prompt的数量,现在就不会导致prompt tokens too long了。但是现在需要有个问题是召回文档准确率很低的问题,你这边有没有什么好的idea可以参考呢

moyangzhan commented 1 month ago

如果用户提问的内容过多,最终向LLM提交的prompt可能不会再携带其他信息(历史记录、召回的文档、知识图谱)

我目前想追求稳定性,所以把AbstractLLMService类中的maxmessages设置为1,减少Prompt的数量,现在就不会导致prompt tokens too long了。但是现在需要有个问题是召回文档准确率很低的问题,你这边有没有什么好的idea可以参考呢

打算加上知识图谱,类似微软graphRAG那样,效果应该会好很多

Linzy0 commented 1 month ago

@moyangzhan 设置RAG的文档切割时重叠数量参数,为什么没有生效呢 文档切割重叠数量意思是文档分段头尾有重叠部分避免检索时信息缺失对么

moyangzhan commented 1 month ago

文档切割重叠数量意思是文档分段头尾有重叠部分避免检索时信息缺失对么

@Linzy0 主要是为了切割时能保留语义连续性

设置RAG的文档切割时重叠数量参数,为什么没有生效呢

重叠数量改动后需要重新生成向量才生效的

Linzy0 commented 1 month ago

@moyangzhan 可以考虑在文本切割分段前先将文本进行AI生成问答对;我尝试用类似fastgpt的QA分段生成问答对,语料切割完也比较完整,并且文段命中率也挺高的; image

moyangzhan commented 1 month ago

@moyangzhan 可以考虑在文本切割分段前先将文本进行AI生成问答对;我尝试用类似fastgpt的QA分段生成问答对,语料切割完也比较完整,并且文段命中率也挺高的; image

@Linzy0 有考虑过生成FAQ,不过是想做为一个单独的列表直接展示在前端页面上,你这种方式倒是一个新思路,我唯一担心的是AI生成问答对之后,与原文相比差异过大,某些特定场景不适用。或者我可以做成可选项,根据使用者的数据特点做选择:1、按段落切割;2、先生成问答对,根据问答对进行切割然后向量化。这样的话还可以加上知识图谱,甚至3种可以自由组合。