如何搭建一个百炼 RAG 应用

方案概览

本案例将给大家演示,如何通过魔笔搭建一个基于RAG的企业知识问答应用。我们会基于百炼的智能体应用,利用魔笔的连接器集成百炼应用,通过魔笔聊天Chat组件搭建一个企业知识问答的 Web 应用。

image

前期准备

  1. 搭建百炼应用,参考百炼文档知识检索增强(RAG);

  2. 发布百炼应用后,获得 API-KEY、应用 ID;

image

说明

建议先在百炼中把对话的效果调试好,后边搭建魔笔应用可以直接看到模型的效果。当魔笔应用已经搭建或发布,只要 API-KEY 和百炼应用 ID 没有变化,再调整百炼应用后,魔笔也会立即看到调整后的效果。

重要

要想体验 RAG 效果,一定要配置自己的百炼的数据中心和知识库,在百炼应用中引用知识库作为“外挂”。

创建集成资源

接下来我们开始搭建魔笔应用,首先配置资源,我们会通过百炼连接器建立一个百炼应用的资源

  1. 进入魔笔控制台,选择资源,选择集成

  2. 创建集成,选择百炼应用连接器

    image

  3. 配置参数,主要是百炼的 API-KEY

    image

  4. 确认保存

说明

这里我们只配置了开发环境,可以按需选择配置生产环境的参数。可以通过给不同环境配置不同的参数,实现环境的不同配置策略。

创建应用和集成操作

  1. 创建魔笔应用,这里我们选择不从模板创建,从零演示搭建过程。(通过模板创建,选择企业知识问答模板,也是不错的选择)

    image

  2. 首先我们创建一个布局,可以按需修改默认参数,如改应用标题、去掉左侧导航等

    image

  3. 切换到首页,并应用布局,效果如下

    image

  4. 下边我们开始搭建对话功能,选择聊天(Chat)组件,拖入到画布中,调整位置如图

    image

  5. 然后我们建立一个百炼的集成操作,主要参数会用到百炼应用 ID,注意打开 SSE 和 has_thoughts。SSE 是流式输出,has_thoughts 属性是返回推理过程。

    image

    重要
    • 注意打开 has_thoughts 属性,这样百炼才会返回推理过程。

    • 配置完成后,就可以点击【运行】测试一下,如果返回正确,效果如图。如果反馈失败,可以看看是否因为提示词 {{chat1.currentMessage.content}} 导致,因为这个时候 chat1 组件可能还没有消息,可以先发起一次对话。

  6. 回到主页面,配置 chat1 组件的交互属性,配置刚刚建立的百炼集成操作,配置好后再次对话,它会回答和你的知识库相关的回答,这里如果信息来自知识库,会有角标,我们稍后会提供更好的展示方式

    image

  7. 我们通过几个属性让对话效果更好看些,比如助手的名称、头像、欢迎语、默认的推荐问题等,你需要用到以下属性:

    属性

    说明

    配置参考

    欢迎信息/类型

    选择卡片,可以配置默认问题(快捷信息)

    卡片

    欢迎信息/内容

    欢迎语

    我是您的办公助手,可以回答工作中常见的问题,有事就请问吧!

    欢迎信息/快捷信息

    可以输入多条推荐的问题

    婚假的规则和申请注意事

    助手内容配置/头像

    助手内容配置/名称

    企业员工助手

    样式/模式

    可以配置是左右对话模式,还是全靠左的文档模式

    聊天

  8. 接下里我们优化回答的展示效果,首先是角标,这里需要用到chat1的增强渲染,把反馈结果中的“ref”标签改为 “a”标签

    image

    配置好后,展示效果如下

    image

高级RAG文档索引和召回展示

下边进行一些高级展示效果的搭建,RAG最有特色的就是大模型反馈的文档索引和召回信息。如果你使用百炼的对话体验,应该可以看到这些特性。那么我们在魔笔中如何实现哪?我们需要使用到【聊天】组件的几个属性

属性

说明

配置参考

内容/信息

自定义的聊天对话内容,数组类型,默认{{[]}}。当需要存储额外信息的时候,需要自己定义。比如我们要存储的文档索引和召回信息。

我们先声明一个数组变量,chats

并引用如:{{chats.value}}

交互/事件/回答完成

这个事件在问题回答完成时候执行,我们这里需要配置执行一段代码。用来把百炼反馈的文档索引等额外信息保存到chats变量中,好在后边自定义渲染中使用

image

助理内容配置/

开启额外区域

和额外信息

开启额外区域开关,配置额外信息(string)可以在反馈的答案下显示定制化信息,支持html片段渲染

image

  1. 创建一个数组变量chats,配置chat1的【内容/信息】属性为{{chats.value}},如图

    imageimage

  2. 编写回答完成后的信息提取代码,并保存到chats数组中

    image

    //获取当前的聊天记录
    let currentDataSource = JSON.parse(JSON.stringify(chat1.context));
    //获取百炼返回的推理过程
    const thoughts = bailian1.rawData?.thoughts;   
    //获取百炼反馈的文档索引信息
    const doc_references = bailian1.rawData?.doc_references;      
    //补充最后当前反馈内容的额外信息
    const length = currentDataSource.length;
    currentDataSource[length - 1].thoughts = thoughts;
    currentDataSource[length - 1].doc_references = doc_references;
    //更新变量chats
    chats.setValue(currentDataSource);

    配置【助理内容配置】额外区域属性,代码如下

    image

    (function generateMarkdown(rawData) {  
      const data = rawData?.thoughts || [];
      
      
      const doc_references = rawData?.doc_references || [];
      
      if (doc_references?.length === 0) {
        return '';
      }
      
      let docRefStr = '<div style="display: flex;color: #5F6E87;margin-bottom: 5px"><div>来源:</div><div>';
      
      rawData?.doc_references.forEach((doc, index) => {
        docRefStr += `<div><ref>[${doc?.index_id}]</ref>《${doc?.doc_name}》</div>\n` 
      })
      
      docRefStr += '</div></div>'
      
      
      if(data.length === 0) {
        return docRefStr + '';
      }
        
      return docRefStr + data.map(item => {
         if (!item?.action) {
           return '';
         };
        
        const docType = item?.action === 'retrieve';
        
        const ragOutput = JSON.parse(item?.observation);
            
        const result = Array.isArray(ragOutput) ? ragOutput.map(ragEl => {
          
          return  `
          
    <details>
     <summary>${ragEl.dataName}(${ragEl.score.toFixed(2)}分)</summary>
     ${ragEl.content}
    </details>
     
          `
        }).join('\n') : ragOutput;
        
           
        
        return `
    <details>
    <summary>推理结果: ${item.action_name || item.action}
    </summary>
    
    
    <h5 style="font-size: 12px;">召回结果:</h5>
    
    ${result}
    
    
    </details>
    
      `
      }).join('\n');
    })(currentItem)

    展示效果如图

    image

    重要

    注意额外信息的类型是字符串类型,代码需要用{{}}表达式

展示RAG索引文档的详细信息

这里通过一个【抽屉】组件来展示索引的详细内容,注意有多角标的形式如【1】【2】,这个时候会同时显示多条文档索引,所以我么会用的【列表】组件来展示。

  1. 首先配置抽屉组件,标题、按钮可以配置,如图

    image

  2. 增加页面变量,用于保存当前的文档信息

    image

  3. 给自定义标签,增加点击操作,执行代码提取当前文档信息

    image

    //角标的解析函数
    function extractArrayFromString(input) {
      // 定义正则表达式以匹配方括号中的数字
      const regex = /\[(\d+)\]/g;
      
      // 创建一个数组来存储匹配到的数字
      const resultArray = [];
      
      // 使用正则表达式匹配,并将结果存储到数组中
      let match;
      while ((match = regex.exec(input)) !== null) {
        resultArray.push(Number(match[1]));
      }
      
      return resultArray;
    }
    
    
    if (child?.[0] && typeof child[0] === 'string') {
      //调用解析函数
      const refIndex = extractArrayFromString(child[0]);
      const doc = (refIndex || []).map(el => currentItem?.doc_references[el - 1])
      //保存到页面的变量中,被抽屉组件内的列表使用
      currentDocRefs.setValue(doc)
      
      if (doc && doc.length) {
        drawerFrame1.show()
      }
      
    }
  4. 配置抽屉组件的内容,展示提取出来的文档信息,因为可能包括多条文档,所以我们使用【列表】组件。其中再增加【文档】组件来展示标题和正文,注意正文可以使用 markdown 格式,还有上对齐,高度自适应等属性。

    image

    image

    image

预览效果

文档RAG应用效果

更多体验

可以参考更多应用模板的实现,一步给RAG应用增加对话历史记录、自定义对话的工具条、知识库操作、用户注销等功能。创建应用时候选择【从模板创建】应用,如图

image