本文介紹如何使用OpenSearch智能問答版為OpenSearch召回引擎版提供RAG能力。
假設使用者已經購買了召回引擎版執行個體,則只需購買智能問答版執行個體,這兩個執行個體的具體作用如下:
召回引擎版執行個體負責:
儲存使用者文檔資料,向量資料
召回使用者文檔資料,向量資料
智能問答版執行個體負責:
對使用者文檔進行切片和向量化 (非必須)
對使用者原始query進行向量化 (非必須)
對召回結果進行推理和總結
具體操作流程如下圖所示:

1. 建立和配置執行個體
1.1. 建立和配置召回引擎版執行個體
1.1.1. 購買OpenSearch召回引擎版執行個體
如果使用者已經有了召回引擎版執行個體,則無需再購買新執行個體。
購買新執行個體可參考購買OpenSearch召回引擎版執行個體。
1.1.2. 配置OpenSearch召回引擎版執行個體
如果使用者不想修改現有召回引擎版執行個體的查詢和召回邏輯,則無需對現有執行個體配置進行修改,只需調用智能問答版的問答介面,對現有召回結果進行推理和總結即可(直接參見3.3章節)。
如果使用者想要對現有執行個體中的資料內容做切片向量化,則需要修改已有配置。
在執行個體列表頁,點擊配置,進入執行個體配置流程,如下圖所示:

1.1.2.1. 表基礎資訊&&資料來源配置
配置表基礎資訊:需要自訂表名稱,設定分區數,設定資料更新資源數:

資料更新資源數預設有2個免費資源,資料量超過2,按n-2 計費,n是單表的資料更新資源總數
資料同步:配置全量資料來源,選擇API資料來源

點擊確定後,資料來源會儲存至列表頁,點擊下一步,進入索引結構配置頁面。
1.1.2.2. 索引結構配置
為了儲存使用者切片資料、向量資料,使用者表結構中必須包含以下欄位:
文檔主鍵,如下圖中的doc_id;
文檔切片後內容欄位,如下圖中的split_content;
文檔切分後內容向量化值,如下圖中的split_content_embedding。該欄位需要設定為多值float類型的向量欄位,並以","分割。
除上述必須欄位外,客戶可以自訂添加其他業務欄位,並使用這些欄位做查詢,排序,過濾等,來滿足客戶的業務需求。如下圖所示:

索引配置說明:
主鍵ID需要配置主鍵索引:PRIMARYKEY64
content和split_content需要配置PACK索引或者TEXT索引
split_content_embedding需要配置CUSTOMIZED,向量索引維度為1536維,其他參數預設即可
索引配置,如下圖所示:

配置完成後,點擊確認建立:

可在功能擴充>變更歷史中查看建立進度,進度完成後即可進行查詢測試:

1.2. 建立和配置智能問答版執行個體
1.2.1. 1.2.1. 購買和配置OpenSearch智能問答版執行個體
購買執行個體可參考通過控制台實現企業知識庫問答。
智能問答版執行個體無需特殊配置,執行個體購買完成即可使用。
2. 資料入庫
2.1. 對文檔內容進行切片和向量化
在使用者原始文檔內容比較大的情況下,無法直接用原始文檔直接調用智能問答版對話介面進行推理總結。
這就需要調用智能問答版執行個體相關endpoint和api,對使用者原始文檔內容進行切片和向量化。
請參考本文末尾的JAVA SDK Demo,或者參考文章:
2.2. 將切片和向量化之後的文檔推送到召回引擎版執行個體
在2.1返回結果中:
chunk_id為切分後的文檔ID。需要和原始文檔ID拼接起來,組成新的文檔主鍵:doc_id
chunk為切分後文檔內容,對應split_content;
embedding為切分後文檔內容向量化值,對應split_content_embedding;
需要將上述結果推送到召回引擎版執行個體。
調用召回引擎版執行個體相關endpoint和API,請參考本文末尾的JAVA SDK Demo,或者參考文章:
3. 查詢問答
3.1. query向量化
將使用者原始查詢內容進行向量化。
調用智能問答版執行個體相關endpoint和API,請參考本文末尾的JAVA SDK Demo,或者參考文章:
3.2. 召迴向量化結果資料
使用query的向量化的結果,去召回引擎版進行召回
處理query的向量化召回外,使用者可以使用其他表欄位進行召回,還可以對召回結果進行排序,過濾等
調用召回引擎版執行個體相關endpoint和API,請參考本文末尾的JAVA SDK Demo,或者參考文章:
3.3. 大模型推理總結
基於召回引擎版召回的結果,調用智能問答版的大模型問答介面,進行推理總結。
請參考本文末尾的JAVA SDK Demo,或者參考文章:
4. JAVA SDK Demo
使用Java SDK完成資料推送和智能問答的demo如下:
添加Maven依賴:
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-sdk-ha3engine</artifactId>
<version>1.3.6</version>
</dependency>
<dependency>
<groupId>com.aliyun.opensearch</groupId>
<artifactId>aliyun-sdk-opensearch</artifactId>
<version>4.0.0</version>
</dependency>JAVA Demo:
import com.aliyun.ha3engine.Client;
import com.aliyun.ha3engine.models.*;
import com.aliyun.ha3engine.vector.models.QueryRequest;
import com.aliyun.opensearch.OpenSearchClient;
import com.aliyun.opensearch.sdk.generated.OpenSearch;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchResult;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.util.*;
public class RetrievalWithLLMDemo {
/**
* 智能問答版執行個體應用程式名稱
*/
private static String llmAppName = "xxxx";
/**
* 智能問答版執行個體訪問地址
*/
private static String llmHost = "http://opensearch-cn-shanghai.aliyuncs.com";
/**
* 智能問答版執行個體訪問key
*/
private static String llmAccessKey = "xxx";
/**
* 智能問答版執行個體存取金鑰
*/
private static String llmAccessSecret = "xxx";
/**
* 召回執行個體API網域名稱
*/
private static String retrievalEndpoint = "ha-cn-xxx.public.ha.aliyuncs.com";
/**
* 召回引擎版執行個體名稱
*/
private static String retrievalInstanceId = "ha-cn-xxx";
/**
* 召回引擎版文檔資料表名稱
*/
private static String retrievalTableName = "ha-cn-xxx";
/**
* 召回引擎版文檔推送的文檔主鍵欄位.
*/
private static String retrievalPkField = "doc_id";
/**
* 召回引擎版使用者名稱
*/
private static String retrievalUserName = "xxx";
/**
* 召回引擎版密碼
*/
private static String retrievalPassword = "xxx";
public static void main(String[] args) throws Exception {
//建立訪問智能問答版執行個體的對象
//建立並構造OpenSearch對象
OpenSearch openSearch = new OpenSearch(llmAccessKey, llmAccessSecret, llmHost);
//建立OpenSearchClient對象,並以OpenSearch對象作為構造參數
OpenSearchClient llmClient = new OpenSearchClient(openSearch);
//建立訪問召回引擎版執行個體的對象
Config config = new Config();
config.setEndpoint(retrievalEndpoint);
config.setInstanceId(retrievalInstanceId);
config.setAccessUserName(retrievalUserName);
config.setAccessPassWord(retrievalPassword);
Client retrievalClient = new Client(config);
//對content進行切分和向量化
Map<String, String> splitParams = new HashMap<String, String>() {{
put("format", "full_json");
put("_POST_BODY", "{\"content\":\"OpenSearch是基於阿里巴巴自主研發的大規模分布式搜尋引擎搭建的一站式商用智能搜尋平台,目前為包括淘寶、天貓、菜鳥在內的阿里集團核心搜尋業務提供中台服務支援。" +
"經過多年的行業搜尋經驗沉澱、雙11大促流量衝擊,智能OpenSearchOpenSearch打磨出一套高效能、高時效、高可用、強穩定搜尋全家桶服務,包括LLM智能問答版、行業演算法版、高效能檢索版、召回引擎版、召回引擎版五類商品版本,以滿足各行各業的搜尋需求。" +
"OpenSearch以平台服務化的形式,將專業搜尋技術簡單化、低門檻化和低成本化,讓搜尋不再成為客戶的業務瓶頸,以低成本實現產品搜尋功能並快速迭代\",\"use_embedding\":true}");
}};
String splitPath = String.format("/apps/%s/actions/knowledge-split", llmAppName);
OpenSearchResult openSearchResult = llmClient.callAndDecodeResult(splitPath, splitParams, "POST");
System.out.println("split result:" + openSearchResult.getResult());
JsonArray array = JsonParser.parseString(openSearchResult.getResult()).getAsJsonArray();
// 文檔推送外層結構, 可添加對文檔操作的結構體. 結構內支援一個或多個文檔操作內容.
ArrayList<Map<String, ?>> documents = new ArrayList<>();
//假設使用者原始文檔主鍵為1
String doc_raw_id="001";
for(JsonElement element:array){
JsonObject object = element.getAsJsonObject();
// 添加文檔
Map<String, Object> add2Document = new HashMap<>();
Map<String, Object> add2DocumentFields = new HashMap<>();
// 插入文檔內容資訊, keyValue 成對匹配.
// field_pk 欄位需與 pkField 欄位配置一致.
add2DocumentFields.put("doc_id", doc_raw_id+"_"+object.get("chunk_id").getAsString());
add2DocumentFields.put("doc_raw_id", doc_raw_id);
List<Float> vectors = new ArrayList();
for(String str: object.get("embedding").getAsString().split(",")){
vectors.add(Float.parseFloat(str));
}
add2DocumentFields.put("split_content_embedding", vectors);
add2DocumentFields.put("split_content", object.get("chunk"));
// 將文檔內容添如 add2Document 結構.
add2Document.put("fields", add2DocumentFields);
// 新增對應的文檔命令: add
add2Document.put("cmd", "add");
documents.add(add2Document);
}
System.out.println("push docs:"+documents.toString());
// 推送資料到召回引擎版
PushDocumentsRequestModel request = new PushDocumentsRequestModel();
request.setBody(documents);
PushDocumentsResponseModel response = retrievalClient.pushDocuments(retrievalTableName, retrievalPkField, request);
String responseBody = response.getBody();
System.out.println("push result:" + responseBody);
//對使用者原始query進行向量化
Map<String, String> embeddingParams = new HashMap<String, String>() {
{
put("format", "full_json");
put("_POST_BODY", "{\"content\":\"OpenSearch是什麼\",\"query\":true}");
}};
String embeddingPath = String.format("/apps/%s/actions/knowledge-embedding", llmAppName);
openSearchResult = llmClient.callAndDecodeResult(embeddingPath, embeddingParams, "POST");
System.out.println("query embedding:"+openSearchResult.getResult());
String embedding = openSearchResult.getResult();
SearchRequestModel haQueryRequestModel = new SearchRequestModel();
SearchQuery haRawQuery = new SearchQuery();
haRawQuery.setQuery("query=split_content_embedding:'"+embedding+"'&&config=start:0,hit:5,format:json&&cluster=general");
haQueryRequestModel.setQuery(haRawQuery);
// 僅支援GET和POST請求方式,預設為GET,查詢query長度超過30K請使用POST請求
haQueryRequestModel.setMethod("POST");
SearchResponseModel searchResponse = retrievalClient.Search(haQueryRequestModel);
System.out.println("搜尋結果:" + searchResponse.getBody());
JsonObject recallResult = JsonParser.parseString(searchResponse.getBody()).getAsJsonObject().get("result").getAsJsonObject();
long hits = recallResult.get("totalHits").getAsLong();
List<String> list = new ArrayList<>();
if(hits <=0){
System.out.println("未能召回結果");
return ;
}else{
JsonArray items = recallResult.get("items").getAsJsonArray();
for(JsonElement element:items) {
JsonObject object = element.getAsJsonObject();
String splitContent = object.get("fields").getAsJsonObject().get("split_content").getAsString();
list.add(splitContent);
}
}
//調用智能問答版執行個體進行推理總結
StringBuffer sb =new StringBuffer();
sb.append("{ \"question\" : \"什麼是opensearch\" ,");
sb.append(" \"type\" : \"text\",");
sb.append(" \"content\" : [");
for(String str:list){
sb.append("\"");
sb.append(str);
sb.append("\"");
sb.append(",");
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append("]}");
Map<String, String> llmParams = new HashMap<String, String>() {{
put("format", "full_json");
put("_POST_BODY", sb.toString());
}};
System.out.println("llm request params:"+llmParams);
String llmPath = String.format("/apps/%s/actions/knowledge-llm", llmAppName);
openSearchResult = llmClient.callAndDecodeResult(llmPath, llmParams, "POST");
System.out.println("llm result:"+openSearchResult.getResult());
}
}