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
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 鉴权信息缺失");
|
|
}
|
|
}
|