本地 RAG 知识库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

149 lines
5.1 KiB

package com.wok.supportbot.controller;
import com.wok.supportbot.app.AssistantApp;
import com.wok.supportbot.entity.ApiKey;
import com.wok.supportbot.entity.SearchResult;
import com.wok.supportbot.rag.HybridSearchService;
import com.wok.supportbot.rag.SearchMode;
import com.wok.supportbot.service.ApiKeyService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 开放 API 接口
* 提供第三方系统调用的对话和检索能力,鉴权由 ApiKeyAuthFilter 处理。
*/
@Slf4j
@RestController
@RequestMapping("/open-api")
public class OpenApiController {
@Autowired
private AssistantApp assistantApp;
@Autowired
private HybridSearchService hybridSearchService;
@Autowired
private ApiKeyService apiKeyService;
/**
* 同步对话接口
*
* @param message 用户消息
* @param roleId 客服角色ID(可选)
* @param chatId 会话ID(可选,不传则自动生成)
* @param request HTTP 请求(含已鉴权的 API Key 信息)
* @return AI 回答
*/
@PostMapping("/chat")
public ResponseEntity<Map<String, Object>> chat(
@RequestParam String message,
@RequestParam(required = false) String roleId,
@RequestParam(required = false) String chatId,
HttpServletRequest request) {
try {
ApiKey apiKey = getApiKeyFromRequest(request);
String resolvedChatId = (chatId != null && !chatId.isBlank())
? chatId
: "openapi-" + apiKey.getId() + "-" + System.currentTimeMillis();
String reply = assistantApp.doChat(message, resolvedChatId);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("data", Map.of(
"reply", reply,
"chatId", resolvedChatId
));
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("开放 API 对话失败", e);
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "对话失败:" + e.getMessage()
));
}
}
/**
* SSE 流式对话接口
*
* @param message 用户消息
* @param chatId 会话ID(可选)
* @param request HTTP 请求
* @return SSE 流式回答
*/
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(
@RequestParam String message,
@RequestParam(required = false) String roleId,
@RequestParam(required = false) String chatId,
HttpServletRequest request) {
ApiKey apiKey = getApiKeyFromRequest(request);
String resolvedChatId = (chatId != null && !chatId.isBlank())
? chatId
: "openapi-stream-" + apiKey.getId() + "-" + System.currentTimeMillis();
return assistantApp.doChatByStream(message, resolvedChatId);
}
/**
* 知识库检索接口
*
* @param query 查询文本
* @param topK 返回条数(默认 5)
* @param searchMode 检索模式:VECTOR / KEYWORD / HYBRID(默认 VECTOR)
* @return 检索结果列表
*/
@PostMapping("/rag/search")
public ResponseEntity<Map<String, Object>> ragSearch(
@RequestParam String query,
@RequestParam(defaultValue = "5") int topK,
@RequestParam(defaultValue = "VECTOR") String searchMode) {
try {
SearchMode mode;
try {
mode = SearchMode.valueOf(searchMode.toUpperCase());
} catch (IllegalArgumentException e) {
mode = SearchMode.VECTOR;
}
List<SearchResult> results = hybridSearchService.search(
query, mode, topK, 0.0, Collections.emptyList());
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("data", results);
result.put("total", results.size());
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("开放 API 检索失败", e);
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "检索失败:" + e.getMessage()
));
}
}
/**
* 从 request attribute 获取已鉴权的 API Key 信息
*/
private ApiKey getApiKeyFromRequest(HttpServletRequest request) {
Object attr = request.getAttribute("apiKey");
if (attr instanceof ApiKey apiKey) {
return apiKey;
}
throw new IllegalStateException("API Key 鉴权信息缺失");
}
}