OpenSearch 讲解

什么是OpenSearch

开放搜索(OpenSearch)是一款结构化数据搜索托管服务,为移动应用开发者和网站站长提供简单、高效、稳定、低成本和可扩展的搜索解决方案。

OpenSearch基于阿里巴巴自主研发的大规模分布式搜索引擎平台,该平台承载了阿里巴巴全部主要搜索业务,包括淘宝、天猫、一淘、1688、ICBU、神马搜索等业务。OpenSearch以平台服务化的形式,将专业搜索技术简单化、低门槛化和低成本化,让搜索引擎技术不再成为客户的业务瓶颈,以低成本实现产品搜索功能并快速迭代。
使用OpenSearch搭建搜索服务,您只需:

  1. 创建搜索应用
  2. 编辑您的应用结构
  3. 上传数据
  4. 从您的网站或应用程序提交搜索请求
    简单、高效、低成本和可扩展。但要是后期用好,还需慢慢调试。

Open Search 和 Elastic Search对比

open search

优点:

  1. 支持用户上传数据或同步云数据,实时性有保障。(可以节省1-2台服务器)

  2. 应用结构、排序相关性自由定制,搜索服务更个性化。可以自定义粗排精排算法,但是LCU 和查询命中的文档、召回的文档、formula的复杂度、查询的复杂度等都有关系。

  3. 基于阿里巴巴在搜索领域的积累,提供查询分析功能,对用户查询词进行纠错、词权重分析、停用词过滤,让搜索服务更智能。可以自定义分词器,下拉提示等。可以很方便的设置召回结果的粗排精排,并且内置了一些对应的函数。

    下拉提示例子,连衣裙 这个query,可以通过如下方式查询得到:
    中文前缀:连,连衣;
    全拼前缀:l, li, lian, lianyi, lianyiqun, …
    简拼前缀:l, ly, lyq;
    汉字加拼音: 连yi, 连衣qun;
    并且下拉提示可以进行人工干预如推荐名单和黑名单。

  4. 可视化的界面、丰富的模板,不用精通代码也能快速创建自己的搜索应用。

  5. 一张OpenSearch表可以支持多个rds及TDDL(mysql)来源表(如分库分表的场景),并且还有一些字段处理插件,这个挺适合咱们的博客场景
    在这里插入图片描述

  6. 提供A/Btest功能,方便进行优化(快速迭代算法)
    在这里插入图片描述
    缺点

  7. open search和es的命中文档数差了一个数量级 通用分词 和es中ik_smark对比

  8. 子账号没有权限

  9. 分词不能使用or进行query

  10. 目前主辅表,仅支持 N:1 或 1:1 的关系,不支持 1:N(即多表数据关联关系中,多的一方只能是主表,且主表只能有1个)。可以进行表拆分或者合并进行应对。但是改动可能较大。

  11. 主辅表需通过应用表外键与附表主键进行数据关联,且表外键只能关联辅表主键。

  12. 最多只支持2层关联。

    多表数据关联支持
    表a->表b,表b->表c
    表a->表d
    不支持超过2层多表数据关联
    表a->表b,表b->表c,表c->表d
    不支持环状多表数据关联
    表a->表b,表b->表a

ElasticSearch

1、在查询方面更加灵活
2、需要自己实现etl工作

对比

数据准备
问答数据:文档数257,250,存储容量为815M。

OpenSearch 和 es 查询语句限制

  1. open search直接使用默认的粗排精排算法
    在这里插入图片描述
  2. es和open search 都查询title 和body
    LCU 使用情况
    目前ask的数据测试query,并发25左右,单次查询均值在5ms以内,平均值LCU在0.3左右。
    人工评估结果
    es查询评估平均得分3.50,opensearch查询评估平均得分2.76

Open Search 的创建

一共有三种创建方式:

  1. 通过模板创建应用结构
  2. 通过上传文档创建应用结构
  3. 通过数据源创建应用结构
    步骤:
    1).添加表
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    2).手动修改创建的应用结构
    在这里插入图片描述
    3). 定义索引结构
    1⃣️需放到 query子句中的字段,必须创建为索引(浮点型不支持创建为索引),分词方式详情请参见字段和分词类型。
    需放到 filter子句,sort子句,及函数中涉及字段有明确标识,需设置为属性的字段必须创建为属性。
    分词字段类型无法配置为属性,例如 TEXT,SHORT_TEXT等都不支持,只支持数值字段类型及不分词字段类型配置为属性,例如 int,int_array,float,float_array,double,double_array,literal,literal_array 等字段类型。
    在这里插入图片描述
    同步数据源
    在这里插入图片描述

创建成功
在这里插入图片描述
数据上传
上面我们是以RDS为例,激活应用后会默认开始导入全量数据,可以在应用管理 - 基本配置 - 索引重建中看到具体进度。

我们对Open Search的使用

API 分类

在这里插入图片描述

搜索方式

可以通过http(get、post)形式或Java、PHP sdk方式进行搜索与上传。

目前在用的产品

APP中@ 操作
在这里插入图片描述
具体的Java 代码

  1. 添加maven
<dependency>
  <groupId>com.aliyun.opensearch</groupId>
  <artifactId>aliyun-sdk-opensearch</artifactId>
  <version>3.2.0</version>
</dependency>
  1. 部分代码
private static SearcherClient searcherClient=null;
    static {
        //创建并构造OpenSearch对象
        OpenSearch openSearch = new OpenSearch(Constants.ACCESSKEY, Constants.SECRET, Constants.HOST);
        //创建OpenSearchClient对象,并以OpenSearch对象作为构造参数
        OpenSearchClient serviceClient = new OpenSearchClient(openSearch);
        //创建SearcherClient对象,并以OpenSearchClient对象作为构造参数
        searcherClient = new SearcherClient(serviceClient);
    }

    /**
     * 配置信息
     * @return
     */
    public Config getConfig(List<String> appNames,int start,int hit,List<String> fields){
        //定义Config对象,用于设定config子句参数,指定应用名,分页,数据返回格式等等
        Config config = new Config(appNames);
        config.setStart(start);
        config.setHits(hit);
        //设置返回格式为fulljson格式
        config.setSearchFormat(SearchFormat.JSON);
        // 设置搜索结果返回应用中哪些字段
        config.setFetchFields(fields);
        return config;
    }

    /**
     * 粗排精排
     * @return
     */
    public Rank getRank(int size){
        if(size>500){
            size=500;
        }
      // 设置精排文档
       Rank rank=new Rank();

        rank.setReRankSize(size);
        return rank;
    }

    /**
     * @param jsonParam 传过来的参数
     *                  {
     *       "filter":"username=\"Joanna_or_zhouzhou\"",//按照某个字段过滤
     *       "reRankSize":"500",//参与精排的条数
     *       "fetchFields":"id,title,nickname",//获取的域/字段
     *       "pageSize":"20",//每页多少条
     *       "index":"nickname",//索引
     *       "page":"0",//第几页 注:0为第一页
     *       "sort":"-id",  //-按照某个字段降序 + 按照某个字段增序  最好不要使用sort 耗资源
     *       "queryWord":"巴掌大的脚印" , //搜索词
     *       "summary":[
     *               {
     *               "snippet":"1",//片段数量
     *               "field":"nickname",//指定的生效的字段。此字段必需为可分词的text类型的字段。
     *               "len":"50",//片段长度
     *               "ellipsis":"...",//片段链接符
     *               "element":"em"//飘红标签
     *               }
     *           ]
     *       }
     * @return
     */
    public ResultVo dataDispose(com.alibaba.fastjson.JSONObject jsonParam){
        ResultVo resultVo=new ResultVo();
        com.alibaba.fastjson.JSONArray jsonArray=new com.alibaba.fastjson.JSONArray();
        List<String> appNames=new ArrayList<>();
        String appName=jsonParam.getString("appName");
        if(StringUtils.isEmpty(appName)){
            resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0001);
            resultVo.setMessage("app应用名为空");
            resultVo.setData(jsonArray);
            return resultVo;
        }else{
            appNames.add(appName);
        }

        if(jsonParam==null || jsonParam.size()<=0){
            resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0002);
            resultVo.setMessage("app参数为空");
            resultVo.setData(jsonArray);
            return resultVo;
        }
        //搜索词
        String queryWord=jsonParam.getString("queryWord");
        if(queryWord==null || queryWord.isEmpty()){
            resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0003);
            resultVo.setMessage("queryWord为必填项不能为空");
            resultVo.setData(jsonArray);
            return resultVo;
        }else{
            queryWord=queryWord.replaceAll("\\\\"," ").replaceAll("\'"," ");
        }
        int start=0;
        int hit=20;
        //第几页 注:0为第一页  config=start:20, hit:20, format:xml
        String page=jsonParam.getString("page");
        //每页多少条
        String pageSize=jsonParam.getString("pageSize");
        //opensearch start+hit<=5000,超过5000会直接报错无结果。
        if(page!=null && NumberUtils.isDigits(page) && Integer.valueOf(page)>=0){
            if(pageSize!=null &&  NumberUtils.isDigits(pageSize) && Integer.valueOf(pageSize)>0){
                hit=Integer.valueOf(pageSize);
            }
            start=Integer.valueOf(page)*hit;
            if(start+hit>Constants.MAXSEARCHRESULT){
                resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0004);
                resultVo.setMessage("查询总数不能超过5000");
                resultVo.setData(jsonArray);
                return resultVo;
            }
        }
        List<String> fetchFields=new ArrayList<>();
        //获取的域/字段
        String fetchFieldStr=jsonParam.getString("fetchFields");
        if(fetchFieldStr==null){
            resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0005);
            resultVo.setMessage("获取结果域为必填项");
            resultVo.setData(jsonArray);
            return resultVo;
        }else{
            fetchFields.addAll(Arrays.asList(fetchFieldStr.split(",")));
        }
        Config config = getConfig(appNames,start,hit,fetchFields);
        SearchParams searchParams = new SearchParams(config);
        //参与精排的条数
        String reRankSize=jsonParam.getString("reRankSize");
        if(reRankSize!=null && NumberUtils.isDigits(reRankSize)){
            searchParams.setRank(getRank(Integer.valueOf(reRankSize)));
        }else{
            searchParams.setRank(getRank(200));
        }
        //索引
        String index=jsonParam.getString("index");
        //-按照某个字段降序 + 按照某个字段增序  最好不要使用sort 耗资源
        String queryStr="";
        if(index==null){
            resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0007);
            resultVo.setMessage("索引不能为空");
            resultVo.setData(jsonArray);
            return resultVo;
        }else{
            queryStr=index+":"+"'"+queryWord+"'";
        }
        //按照某个字段过滤
        String filter=jsonParam.getString("filter");
        if(filter!=null){
            if(filter.indexOf("=")>-1){
                int eindex = filter.indexOf("=");
                String pre = filter.substring(0, eindex);
                String next = filter.substring(eindex + 1);
                queryStr+=" AND "+pre+":"+next;
            }
//            //这里针对CSDN_User 做处理  提高查询效率
//            if("CSDN_User".equals(appName)){
//
//            }else{
//                searchParams.setFilter(filter);
//            }
        }
        String sort=jsonParam.getString("sort");
        if(sort!=null){
            queryStr=queryStr+"&&sort="+sort;
        }
        log.info("querystr:"+queryStr);
        searchParams.setQuery(queryStr);
        SearchParamsBuilder paramsBuilder = SearchParamsBuilder.create(searchParams);
        try {
            JSONArray summary = jsonParam.getJSONArray("summary");
            if(summary!=null && summary.size()>0){
                for (int i=0;i<summary.size();i++){
                    com.alibaba.fastjson.JSONObject jsonObject = summary.getJSONObject(i);
                    String field = jsonObject.getString("field");
                    int snippet=1;
                    String snippetStr = jsonObject.getString("snippet");
                    if(snippetStr!=null && NumberUtils.isDigits(snippetStr)){
                        snippet=Integer.valueOf(snippetStr);
                    }
                    int len=50;
                    String lenStr = jsonObject.getString("len");
                    if(lenStr!=null && NumberUtils.isDigits(lenStr)){
                        len=Integer.valueOf(lenStr);
                    }
                    String ellipsis = jsonObject.getString("ellipsis");
                    String element = jsonObject.getString("element");
                    paramsBuilder.addSummary(field,len,element,ellipsis,snippet);
                }

            }
        }catch (Exception e){
            resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0006);
            resultVo.setMessage(e.getMessage());
            resultVo.setData(jsonArray);
            return resultVo;
        }
        try {
            SearchResult searchResult = searcherClient.execute(paramsBuilder);
            String result = searchResult.getResult();
            JSONObject obj = new JSONObject(result);
            Object status = obj.get("status");
            if(Constants.OK.equals(status+"")){
                JSONObject map = (JSONObject)obj.get("result");
                Object items = map.get("items");
                resultVo.setData(JSON.parseArray(items.toString()));
                resultVo.setCode("200");
                resultVo.setMessage("查询成功");
                com.aliyun.opensearch.sdk.dependencies.org.json.JSONArray compute_cost = (com.aliyun.opensearch.sdk.dependencies.org.json.JSONArray)map.get("compute_cost");
                if(compute_cost!=null && compute_cost.length()>0){
                    JSONObject jsonObject = compute_cost.getJSONObject(0);
                    Object lcu = jsonObject.get("value");
                    log.info("查询成功,消耗LCU:"+lcu);

                }
            }else{
                resultVo.setData(jsonArray);
                resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE00010);
                Object errors = obj.get("errors");
                if(errors!=null){
                    resultVo.setMessage(errors+"");
                }else{
                    resultVo.setMessage("查询失败");
                }
                log.error("查询失败:"+errors);
                return resultVo;
            }

        } catch (OpenSearchException e) {
            resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0008);
            resultVo.setMessage(e.getMessage());
            resultVo.setData(jsonArray);
            log.error(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0008+" : "+e.getMessage());
            return resultVo;
        } catch (OpenSearchClientException e) {
            resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0009);
            resultVo.setMessage(e.getMessage());
            resultVo.setData(jsonArray);
            log.error(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0009+" : "+e.getMessage());
            return resultVo;
        }
        return resultVo;
    }
    public ResultVo searchData(com.alibaba.fastjson.JSONObject jsonParam) {
        ResultVo resultVo = dataDispose(jsonParam);
        return resultVo;
    }

遇到的问题

  1. 表关联问题
  2. 性能问题 (filter 字段建立索引)
  3. 子账号没有权限导入数据
  4. 目前redis中的数据不能进行关联同步
  5. query分词之后只能and进行搜索

引用:官方文档

备注:

  1. LCU是用来衡量搜索应用计算能力的单位,一个LCU代表搜索集群中10millicores的计算能力,计算资源估算方法:LCU个数=QPS*compute_cost,millicores是CPU资源的单位,即一个核的1/1000, compute_cost是单次查询计算消耗的LCU
<think>好的,用户想了解Tavily的使用教程和功能详解。首先,我需要确认Tavily是什么。根据提供的引用,它被用于构建自适应RAG系统,结合LangGraph和OpenAI。这说明Tavily可能是一个搜索API或数据检索工具,用于增强生成式AI的效果。 接下来,我得整理Tavily的主要功能。通常这类工具可能提供网络搜索、数据聚合、结果过滤等功能。可能需要提到它的API接口如何集成到现有系统中,比如在RAG中的应用场景。此外,用户可能需要知道如何注册、获取API密钥,以及基础的代码示例。 然后,使用教程部分需要分步骤讲解。比如安装SDK、设置API密钥、基本搜索调用、高级参数(如深度搜索、结果数量、域名过滤等)。需要确保代码示例正确,并符合用户提供的格式要求,比如用Python语言,使用正确的语法。 还要注意用户提到的系统级指令,比如数学公式的格式,但这里可能不太涉及。不过代码块需要用三个反引号包裹,并标明语言。另外,生成的相关问题要围绕Tavily的功能、与其他工具的结合、性能优化等展开。 需要检查引用是否正确,用户提供的引用是关于自适应RAG系统的,可能涉及Tavily在其中的作用,但具体到Tavily的详细文档可能需要参考官方资源。如果引用中的GitHub代码有具体使用Tavily的部分,可以引导用户查看相关部分。 最后,确保回答结构清晰,分点说明,语言简洁,符合中文表达习惯。可能还需要提醒用户注意API调用限制和费用,以及最佳实践,比如错误处理和异步调用。</think>### Tavily功能详解及使用教程 Tavily是基于AI的实时搜索API工具,专为LLM应用设计,可集成到RAG(检索增强生成)系统中提升信息检索效率[^1]。 **核心功能:** 1. **智能搜索优化**:自动解析复杂查询,优先返回学术论文、权威网站等高价值内容 2. **结果结构化输出**:返回包含`answer`、`content`、`images`等多模态数据的JSON格式 3. **搜索深度控制**:支持通过`depth`参数设置基础搜索(basic)或深度搜索(advanced) 4. **领域过滤**:可通过`include_domains`参数指定搜索范围(如`*.edu`教育类网站) ### 使用教程(Python环境) **步骤1:安装SDK** ```python !pip install tavily-python ``` **步骤2:初始化客户端** ```python from tavily import TavilyClient tavily = TavilyClient(api_key="your_api_key") # 官网注册获取密钥 ``` **步骤3:基础搜索调用** ```python response = tavily.search(query="2023年全球GDP排名前五的国家") print(response["answer"]) # 直接获取AI解析后的答案 ``` **步骤4:高级参数应用** ```python advanced_result = tavily.search( query="生成对抗网络的最新研究进展", search_depth="advanced", # 深度搜索模式 max_results=10, include_domains=["arxiv.org"] ) ``` ### 结果解析示例 ```json { "query": "量子计算在密码学中的应用", "answer": "量子计算对RSA等非对称加密算法构成威胁...", "content": [ { "title": "NIST后量子密码标准化项目", "url": "https://www.nist.gov/pqcrypto", "text": "美国国家标准与技术研究院正在..." } ], "images": ["https://example.com/quantum-chip.jpg"] } ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值