589 lines
17 KiB
Markdown
589 lines
17 KiB
Markdown
|
|
# Dify指定知识库实现方案
|
|||
|
|
|
|||
|
|
## 🎯 问题分析
|
|||
|
|
|
|||
|
|
**需求**:在调用Dify对话API时,动态指定使用哪些知识库(Dataset)
|
|||
|
|
|
|||
|
|
**场景**:不同部门用户使用同一个智能体,但只能访问各自授权的知识库
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📋 Dify官方支持的方式
|
|||
|
|
|
|||
|
|
### 方式1:知识库检索API + LLM组合(推荐⭐⭐)
|
|||
|
|
|
|||
|
|
**优点**:
|
|||
|
|
- ✅ 完全控制知识库选择
|
|||
|
|
- ✅ 可以实现复杂的权限逻辑
|
|||
|
|
- ✅ 灵活性最高
|
|||
|
|
|
|||
|
|
**缺点**:
|
|||
|
|
- ⚠️ 需要自己组合API调用
|
|||
|
|
- ⚠️ 无法使用Dify的完整对话管理功能
|
|||
|
|
|
|||
|
|
#### 步骤1:检索相关知识
|
|||
|
|
|
|||
|
|
```http
|
|||
|
|
POST https://api.dify.ai/v1/datasets/{dataset_id}/retrieve
|
|||
|
|
Authorization: Bearer {api_key}
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"query": "如何申请奖学金?",
|
|||
|
|
"top_k": 3,
|
|||
|
|
"score_threshold": 0.7
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**响应示例:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"records": [
|
|||
|
|
{
|
|||
|
|
"content": "申请奖学金需要满足以下条件...",
|
|||
|
|
"score": 0.95,
|
|||
|
|
"metadata": {
|
|||
|
|
"document_id": "doc-123",
|
|||
|
|
"document_name": "奖学金管理办法.pdf"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 步骤2:多知识库并行检索
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Service
|
|||
|
|
public class DifyKnowledgeService {
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从多个知识库检索相关内容
|
|||
|
|
*/
|
|||
|
|
public List<RetrievalRecord> retrieveFromMultipleDatasets(
|
|||
|
|
String query,
|
|||
|
|
List<String> datasetIds,
|
|||
|
|
int topK) {
|
|||
|
|
|
|||
|
|
List<CompletableFuture<List<RetrievalRecord>>> futures = datasetIds.stream()
|
|||
|
|
.map(datasetId -> CompletableFuture.supplyAsync(() ->
|
|||
|
|
retrieveFromDataset(datasetId, query, topK)
|
|||
|
|
))
|
|||
|
|
.collect(Collectors.toList());
|
|||
|
|
|
|||
|
|
// 等待所有检索完成
|
|||
|
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
|||
|
|
|
|||
|
|
// 合并结果并按分数排序
|
|||
|
|
return futures.stream()
|
|||
|
|
.map(CompletableFuture::join)
|
|||
|
|
.flatMap(List::stream)
|
|||
|
|
.sorted((a, b) -> Double.compare(b.getScore(), a.getScore()))
|
|||
|
|
.limit(topK)
|
|||
|
|
.collect(Collectors.toList());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从单个知识库检索
|
|||
|
|
*/
|
|||
|
|
private List<RetrievalRecord> retrieveFromDataset(
|
|||
|
|
String datasetId,
|
|||
|
|
String query,
|
|||
|
|
int topK) {
|
|||
|
|
|
|||
|
|
String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/retrieve");
|
|||
|
|
|
|||
|
|
Map<String, Object> requestBody = new HashMap<>();
|
|||
|
|
requestBody.put("query", query);
|
|||
|
|
requestBody.put("top_k", topK);
|
|||
|
|
requestBody.put("score_threshold", 0.7);
|
|||
|
|
|
|||
|
|
// HTTP请求
|
|||
|
|
HttpResponse response = httpClient.post(url)
|
|||
|
|
.header("Authorization", "Bearer " + apiKey)
|
|||
|
|
.body(requestBody)
|
|||
|
|
.execute();
|
|||
|
|
|
|||
|
|
return parseRetrievalResponse(response);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 步骤3:组合上下文调用LLM
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/**
|
|||
|
|
* 使用检索到的知识回答问题
|
|||
|
|
*/
|
|||
|
|
public String chatWithRetrievedKnowledge(
|
|||
|
|
String query,
|
|||
|
|
List<RetrievalRecord> records,
|
|||
|
|
String conversationId) {
|
|||
|
|
|
|||
|
|
// 构建上下文
|
|||
|
|
String context = records.stream()
|
|||
|
|
.map(r -> "【" + r.getMetadata().get("document_name") + "】\n" + r.getContent())
|
|||
|
|
.collect(Collectors.joining("\n\n"));
|
|||
|
|
|
|||
|
|
// 构建Prompt
|
|||
|
|
String prompt = String.format(
|
|||
|
|
"请基于以下知识库内容回答用户问题。如果知识库中没有相关信息,请明确告知用户。\n\n" +
|
|||
|
|
"知识库内容:\n%s\n\n" +
|
|||
|
|
"用户问题:%s\n\n" +
|
|||
|
|
"回答:",
|
|||
|
|
context, query
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 调用Dify Completion API或直接调用LLM
|
|||
|
|
return callLLM(prompt, conversationId);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 方式2:Dify Workflow(工作流)⭐
|
|||
|
|
|
|||
|
|
**原理**:创建工作流,使用变量控制知识库选择
|
|||
|
|
|
|||
|
|
#### Dify工作流配置
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
workflow_nodes:
|
|||
|
|
- id: start
|
|||
|
|
type: start
|
|||
|
|
outputs:
|
|||
|
|
- query # 用户问题
|
|||
|
|
- dataset_ids # 知识库ID列表(变量)
|
|||
|
|
|
|||
|
|
- id: kb_retrieval
|
|||
|
|
type: knowledge-retrieval
|
|||
|
|
inputs:
|
|||
|
|
query: "{{#start.query#}}"
|
|||
|
|
datasets: "{{#start.dataset_ids#}}" # 从输入变量读取
|
|||
|
|
top_k: 3
|
|||
|
|
outputs:
|
|||
|
|
- result
|
|||
|
|
|
|||
|
|
- id: llm
|
|||
|
|
type: llm
|
|||
|
|
inputs:
|
|||
|
|
prompt: |
|
|||
|
|
基于以下知识库内容回答问题:
|
|||
|
|
{{#kb_retrieval.result#}}
|
|||
|
|
|
|||
|
|
用户问题:{{#start.query#}}
|
|||
|
|
outputs:
|
|||
|
|
- answer
|
|||
|
|
|
|||
|
|
- id: end
|
|||
|
|
type: end
|
|||
|
|
outputs:
|
|||
|
|
- answer: "{{#llm.answer#}}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### API调用示例
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/**
|
|||
|
|
* 调用Dify Workflow
|
|||
|
|
*/
|
|||
|
|
public void chatWithWorkflow(
|
|||
|
|
String query,
|
|||
|
|
List<String> datasetIds,
|
|||
|
|
String userId,
|
|||
|
|
SseEmitter emitter) {
|
|||
|
|
|
|||
|
|
String url = difyConfig.getFullApiUrl("/workflows/run");
|
|||
|
|
|
|||
|
|
Map<String, Object> inputs = new HashMap<>();
|
|||
|
|
inputs.put("query", query);
|
|||
|
|
inputs.put("dataset_ids", datasetIds); // ⭐ 动态传入知识库列表
|
|||
|
|
|
|||
|
|
Map<String, Object> requestBody = new HashMap<>();
|
|||
|
|
requestBody.put("inputs", inputs);
|
|||
|
|
requestBody.put("response_mode", "streaming");
|
|||
|
|
requestBody.put("user", userId);
|
|||
|
|
|
|||
|
|
// 流式请求
|
|||
|
|
httpClient.postStream(url, requestBody, new StreamCallback() {
|
|||
|
|
@Override
|
|||
|
|
public void onChunk(String chunk) {
|
|||
|
|
emitter.send(chunk);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**HTTP请求示例:**
|
|||
|
|
```http
|
|||
|
|
POST /v1/workflows/run
|
|||
|
|
Authorization: Bearer {api_key}
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"inputs": {
|
|||
|
|
"query": "如何申请奖学金?",
|
|||
|
|
"dataset_ids": ["dataset-edu-001", "dataset-public-001"]
|
|||
|
|
},
|
|||
|
|
"response_mode": "streaming",
|
|||
|
|
"user": "user-123"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 方式3:多应用切换(不推荐)
|
|||
|
|
|
|||
|
|
为不同部门创建不同的Dify应用:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
部门A -> App A(绑定知识库A1, A2)
|
|||
|
|
部门B -> App B(绑定知识库B1, B2)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**缺点**:
|
|||
|
|
- ❌ 管理复杂
|
|||
|
|
- ❌ 无法共享公共知识库
|
|||
|
|
- ❌ 扩展性差
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎨 推荐实现方案
|
|||
|
|
|
|||
|
|
### 方案:知识库检索API + 自定义LLM调用
|
|||
|
|
|
|||
|
|
#### 完整实现代码
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Service
|
|||
|
|
public class AiChatServiceImpl implements AiChatService {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private AiKnowledgeMapper knowledgeMapper;
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private DifyApiClient difyApiClient;
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private AiMessageMapper messageMapper;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 流式对话(带知识库权限隔离)
|
|||
|
|
*/
|
|||
|
|
@Override
|
|||
|
|
public void streamChat(
|
|||
|
|
String message,
|
|||
|
|
String conversationId,
|
|||
|
|
String userId,
|
|||
|
|
SseEmitter emitter) {
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 1. 获取当前登录用户的部门角色信息(通过LoginUtil)⭐
|
|||
|
|
List<UserDeptRoleVO> userDeptRoles = LoginUtil.getCurrentDeptRole();
|
|||
|
|
|
|||
|
|
// 2. 查询用户有权限的知识库(自动权限过滤✅)
|
|||
|
|
TbAiKnowledge filter = new TbAiKnowledge();
|
|||
|
|
filter.setStatus(1); // 只查询启用的
|
|||
|
|
|
|||
|
|
List<TbAiKnowledge> authorizedKnowledges =
|
|||
|
|
knowledgeMapper.selectAiKnowledges(
|
|||
|
|
filter,
|
|||
|
|
userDeptRoles // 直接传入LoginUtil获取的用户权限信息
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 3. 提取Dify Dataset IDs
|
|||
|
|
List<String> datasetIds = authorizedKnowledges.stream()
|
|||
|
|
.map(TbAiKnowledge::getDifyDatasetId)
|
|||
|
|
.filter(Objects::nonNull)
|
|||
|
|
.collect(Collectors.toList());
|
|||
|
|
|
|||
|
|
if (datasetIds.isEmpty()) {
|
|||
|
|
emitter.send("您当前没有可访问的知识库,无法进行对话。");
|
|||
|
|
emitter.complete();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. 从多个知识库检索相关内容
|
|||
|
|
List<RetrievalRecord> retrievalRecords =
|
|||
|
|
difyApiClient.retrieveFromMultipleDatasets(
|
|||
|
|
message,
|
|||
|
|
datasetIds,
|
|||
|
|
5 // Top K
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 5. 构建上下文
|
|||
|
|
String context = buildContext(retrievalRecords, authorizedKnowledges);
|
|||
|
|
|
|||
|
|
// 6. 调用LLM流式对话
|
|||
|
|
difyApiClient.streamChatWithContext(
|
|||
|
|
message,
|
|||
|
|
context,
|
|||
|
|
conversationId,
|
|||
|
|
userId,
|
|||
|
|
new StreamCallback() {
|
|||
|
|
private StringBuilder fullAnswer = new StringBuilder();
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public void onChunk(String chunk) {
|
|||
|
|
fullAnswer.append(chunk);
|
|||
|
|
emitter.send(chunk);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public void onComplete() {
|
|||
|
|
// 保存消息
|
|||
|
|
saveMessages(
|
|||
|
|
conversationId,
|
|||
|
|
userId,
|
|||
|
|
message,
|
|||
|
|
fullAnswer.toString(),
|
|||
|
|
retrievalRecords
|
|||
|
|
);
|
|||
|
|
emitter.complete();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public void onError(Throwable error) {
|
|||
|
|
log.error("对话失败", error);
|
|||
|
|
emitter.completeWithError(error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
log.error("流式对话异常", e);
|
|||
|
|
emitter.completeWithError(e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构建上下文
|
|||
|
|
*/
|
|||
|
|
private String buildContext(
|
|||
|
|
List<RetrievalRecord> records,
|
|||
|
|
List<TbAiKnowledge> knowledges) {
|
|||
|
|
|
|||
|
|
Map<String, String> knowledgeTitles = knowledges.stream()
|
|||
|
|
.collect(Collectors.toMap(
|
|||
|
|
TbAiKnowledge::getDifyDatasetId,
|
|||
|
|
TbAiKnowledge::getTitle
|
|||
|
|
));
|
|||
|
|
|
|||
|
|
return records.stream()
|
|||
|
|
.map(r -> {
|
|||
|
|
String datasetId = r.getDatasetId();
|
|||
|
|
String knowledgeTitle = knowledgeTitles.getOrDefault(datasetId, "未知知识库");
|
|||
|
|
return String.format(
|
|||
|
|
"【来源:%s - %s】\n%s",
|
|||
|
|
knowledgeTitle,
|
|||
|
|
r.getDocumentName(),
|
|||
|
|
r.getContent()
|
|||
|
|
);
|
|||
|
|
})
|
|||
|
|
.collect(Collectors.joining("\n\n---\n\n"));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### DifyApiClient实现
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Component
|
|||
|
|
public class DifyApiClient {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private DifyConfig difyConfig;
|
|||
|
|
|
|||
|
|
private final OkHttpClient httpClient;
|
|||
|
|
|
|||
|
|
public DifyApiClient(DifyConfig difyConfig) {
|
|||
|
|
this.difyConfig = difyConfig;
|
|||
|
|
this.httpClient = new OkHttpClient.Builder()
|
|||
|
|
.connectTimeout(difyConfig.getConnectTimeout(), TimeUnit.SECONDS)
|
|||
|
|
.readTimeout(difyConfig.getReadTimeout(), TimeUnit.SECONDS)
|
|||
|
|
.build();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从多个知识库检索
|
|||
|
|
*/
|
|||
|
|
public List<RetrievalRecord> retrieveFromMultipleDatasets(
|
|||
|
|
String query,
|
|||
|
|
List<String> datasetIds,
|
|||
|
|
int topK) {
|
|||
|
|
|
|||
|
|
// 并行检索所有知识库
|
|||
|
|
List<CompletableFuture<List<RetrievalRecord>>> futures =
|
|||
|
|
datasetIds.stream()
|
|||
|
|
.map(id -> CompletableFuture.supplyAsync(() ->
|
|||
|
|
retrieveFromDataset(id, query, topK)
|
|||
|
|
))
|
|||
|
|
.collect(Collectors.toList());
|
|||
|
|
|
|||
|
|
// 等待完成
|
|||
|
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
|||
|
|
|
|||
|
|
// 合并并排序
|
|||
|
|
return futures.stream()
|
|||
|
|
.map(CompletableFuture::join)
|
|||
|
|
.flatMap(List::stream)
|
|||
|
|
.sorted((a, b) -> Double.compare(b.getScore(), a.getScore()))
|
|||
|
|
.limit(topK)
|
|||
|
|
.collect(Collectors.toList());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从单个知识库检索
|
|||
|
|
*/
|
|||
|
|
private List<RetrievalRecord> retrieveFromDataset(
|
|||
|
|
String datasetId,
|
|||
|
|
String query,
|
|||
|
|
int topK) {
|
|||
|
|
|
|||
|
|
String url = String.format(
|
|||
|
|
"%s/datasets/%s/retrieve",
|
|||
|
|
difyConfig.getApiBaseUrl(),
|
|||
|
|
datasetId
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
JSONObject body = new JSONObject();
|
|||
|
|
body.put("query", query);
|
|||
|
|
body.put("top_k", topK);
|
|||
|
|
body.put("score_threshold", 0.7);
|
|||
|
|
|
|||
|
|
Request request = new Request.Builder()
|
|||
|
|
.url(url)
|
|||
|
|
.header("Authorization", "Bearer " + difyConfig.getApiKey())
|
|||
|
|
.header("Content-Type", "application/json")
|
|||
|
|
.post(RequestBody.create(
|
|||
|
|
body.toString(),
|
|||
|
|
MediaType.parse("application/json")
|
|||
|
|
))
|
|||
|
|
.build();
|
|||
|
|
|
|||
|
|
try (Response response = httpClient.newCall(request).execute()) {
|
|||
|
|
if (!response.isSuccessful()) {
|
|||
|
|
throw new DifyException("知识库检索失败: " + response.message());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String responseBody = response.body().string();
|
|||
|
|
return parseRetrievalResponse(datasetId, responseBody);
|
|||
|
|
|
|||
|
|
} catch (IOException e) {
|
|||
|
|
throw new DifyException("知识库检索异常", e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 流式对话(带上下文)
|
|||
|
|
*/
|
|||
|
|
public void streamChatWithContext(
|
|||
|
|
String query,
|
|||
|
|
String context,
|
|||
|
|
String conversationId,
|
|||
|
|
String userId,
|
|||
|
|
StreamCallback callback) {
|
|||
|
|
|
|||
|
|
String url = difyConfig.getApiBaseUrl() + "/chat-messages";
|
|||
|
|
|
|||
|
|
// 构建完整Prompt
|
|||
|
|
String fullPrompt = String.format(
|
|||
|
|
"请基于以下知识库内容回答用户问题。" +
|
|||
|
|
"如果知识库中没有相关信息,请明确告知用户。\n\n" +
|
|||
|
|
"知识库内容:\n%s\n\n" +
|
|||
|
|
"用户问题:%s",
|
|||
|
|
context, query
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
JSONObject body = new JSONObject();
|
|||
|
|
body.put("query", fullPrompt);
|
|||
|
|
body.put("conversation_id", conversationId);
|
|||
|
|
body.put("user", userId);
|
|||
|
|
body.put("response_mode", "streaming");
|
|||
|
|
body.put("inputs", new JSONObject());
|
|||
|
|
|
|||
|
|
Request request = new Request.Builder()
|
|||
|
|
.url(url)
|
|||
|
|
.header("Authorization", "Bearer " + difyConfig.getApiKey())
|
|||
|
|
.header("Content-Type", "application/json")
|
|||
|
|
.post(RequestBody.create(
|
|||
|
|
body.toString(),
|
|||
|
|
MediaType.parse("application/json")
|
|||
|
|
))
|
|||
|
|
.build();
|
|||
|
|
|
|||
|
|
// SSE流式处理
|
|||
|
|
httpClient.newCall(request).enqueue(new Callback() {
|
|||
|
|
@Override
|
|||
|
|
public void onResponse(Call call, Response response) {
|
|||
|
|
if (!response.isSuccessful()) {
|
|||
|
|
callback.onError(new DifyException("对话失败: " + response.message()));
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try (BufferedReader reader = new BufferedReader(
|
|||
|
|
new InputStreamReader(response.body().byteStream()))) {
|
|||
|
|
|
|||
|
|
String line;
|
|||
|
|
while ((line = reader.readLine()) != null) {
|
|||
|
|
if (line.startsWith("data: ")) {
|
|||
|
|
String data = line.substring(6);
|
|||
|
|
if (!"[DONE]".equals(data)) {
|
|||
|
|
JSONObject json = new JSONObject(data);
|
|||
|
|
String chunk = json.optString("answer", "");
|
|||
|
|
if (!chunk.isEmpty()) {
|
|||
|
|
callback.onChunk(chunk);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
callback.onComplete();
|
|||
|
|
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
callback.onError(e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public void onFailure(Call call, IOException e) {
|
|||
|
|
callback.onError(e);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 三种方式对比
|
|||
|
|
|
|||
|
|
| 方案 | 灵活性 | 实现难度 | 性能 | 推荐度 |
|
|||
|
|
|------|--------|----------|------|--------|
|
|||
|
|
| 检索API + 自定义LLM | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
|||
|
|
| Workflow工作流 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
|
|||
|
|
| 多应用切换 | ⭐⭐ | ⭐ | ⭐⭐⭐ | ⭐ |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 最终推荐方案
|
|||
|
|
|
|||
|
|
**使用"检索API + 自定义LLM"方案**
|
|||
|
|
|
|||
|
|
**理由**:
|
|||
|
|
1. ✅ 完全控制知识库访问权限
|
|||
|
|
2. ✅ 可以实现复杂的部门隔离逻辑
|
|||
|
|
3. ✅ 支持并行检索多个知识库
|
|||
|
|
4. ✅ 可以自定义Prompt和上下文
|
|||
|
|
5. ✅ 灵活性最高,适合企业级应用
|
|||
|
|
|
|||
|
|
**实现步骤**:
|
|||
|
|
1. 用户发起对话
|
|||
|
|
2. 根据用户权限查询可访问的知识库(Mapper已实现✅)
|
|||
|
|
3. 并行调用Dify检索API获取相关内容
|
|||
|
|
4. 合并结果构建上下文
|
|||
|
|
5. 调用LLM流式生成答案
|
|||
|
|
6. 保存对话记录(含知识来源)
|
|||
|
|
|
|||
|
|
这样既利用了Dify的知识库能力,又保持了完全的控制权!🎉
|
|||
|
|
|