业务逻辑修改以及完善
This commit is contained in:
7
pom.xml
7
pom.xml
@@ -366,6 +366,13 @@
|
||||
<version>1.0.10</version>
|
||||
</dependency>
|
||||
|
||||
<!-- WebSocket Client -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.tyrus</groupId>
|
||||
<artifactId>tyrus-client</artifactId>
|
||||
<version>2.1.3</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -91,6 +91,12 @@
|
||||
<artifactId>concentus</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- WebSocket Client -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.tyrus</groupId>
|
||||
<artifactId>tyrus-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -1,173 +1,80 @@
|
||||
package com.vetti.socket.agents;
|
||||
|
||||
import okhttp3.*;
|
||||
import okio.ByteString;
|
||||
import org.springframework.web.socket.BinaryMessage;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
|
||||
import javax.websocket.ClientEndpointConfig;
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ElevenLabsAgentClient extends TextWebSocketHandler{
|
||||
@Slf4j
|
||||
public class ElevenLabsAgentClient {
|
||||
|
||||
// 存储Vue会话与ElevenLabs WebSocket的映射(多客户端隔离)
|
||||
private static final Map<WebSocketSession, WebSocket> SESSION_MAP = new ConcurrentHashMap<>();
|
||||
// ElevenLabs配置
|
||||
private static final String ELEVEN_LABS_API_KEY = "你的ElevenLabs API Key";
|
||||
private static final String AGENT_ID = "你的ElevenLabs Agent ID";
|
||||
private static final String ELEVEN_LABS_WSS_URL = "wss://api.elevenlabs.io/v1/agents/" + AGENT_ID + "/stream";
|
||||
// OkHttp客户端
|
||||
private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder()
|
||||
.readTimeout(0, TimeUnit.MILLISECONDS) // WebSocket长连接取消读超时
|
||||
private static final String AGENT_WS_URL =
|
||||
"wss://api.elevenlabs.io/v1/agents/%s/stream";
|
||||
|
||||
|
||||
|
||||
private final ElevenLabsAgentEndpoint endpoint;
|
||||
|
||||
public ElevenLabsAgentClient(String traceId, WebSocketSession frontendSession) {
|
||||
this.endpoint = new ElevenLabsAgentEndpoint();
|
||||
connect(traceId, frontendSession);
|
||||
}
|
||||
|
||||
private void connect(String traceId, WebSocketSession frontendSession) {
|
||||
try {
|
||||
log.info("[traceId={}] Connecting to ElevenLabs Agent...", traceId);
|
||||
|
||||
WebSocketContainer container =
|
||||
ContainerProvider.getWebSocketContainer();
|
||||
|
||||
ClientEndpointConfig config =
|
||||
ClientEndpointConfig.Builder.create()
|
||||
.configurator(new ClientEndpointConfig.Configurator() {
|
||||
@Override
|
||||
public void beforeRequest(
|
||||
Map<String, List<String>> headers
|
||||
) {
|
||||
headers.put(
|
||||
"xi-api-key",
|
||||
List.of("sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116")
|
||||
);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
// ==================== 1. 处理Vue前端连接 ====================
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession vueSession) throws Exception {
|
||||
super.afterConnectionEstablished(vueSession);
|
||||
System.out.println("Vue前端连接成功:" + vueSession.getId());
|
||||
config.getUserProperties().put("traceId", traceId);
|
||||
config.getUserProperties().put("frontendSession", frontendSession);
|
||||
|
||||
// 建立与ElevenLabs Agents的WSS连接
|
||||
buildElevenLabsWssConnection(vueSession);
|
||||
}
|
||||
container.connectToServer(
|
||||
endpoint,
|
||||
config,
|
||||
URI.create(
|
||||
String.format(
|
||||
AGENT_WS_URL,
|
||||
"9c5cb2f7ba9efb61d0f0eee01427b6e00c6abe92d4754cfb794884ac4d73c79d"
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// ==================== 2. 处理Vue前端发送的文本消息(如语音指令、配置信息) ====================
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession vueSession, TextMessage message) throws Exception {
|
||||
super.handleTextMessage(vueSession, message);
|
||||
System.out.println("接收Vue文本消息:" + message.getPayload());
|
||||
// 获取对应ElevenLabs WebSocket连接,转发消息
|
||||
WebSocket elevenLabsWs = SESSION_MAP.get(vueSession);
|
||||
if (elevenLabsWs != null && elevenLabsWs.queueSize() == 0) {
|
||||
elevenLabsWs.send(message.getPayload());
|
||||
System.out.println("转发文本消息到ElevenLabs成功");
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 3. 处理Vue前端发送的二进制消息(核心:语音流数据) ====================
|
||||
@Override
|
||||
protected void handleBinaryMessage(WebSocketSession vueSession, BinaryMessage message) {
|
||||
super.handleBinaryMessage(vueSession, message);
|
||||
byte[] voiceData = message.getPayload().array();
|
||||
System.out.println("接收Vue语音流数据,字节长度:" + voiceData.length);
|
||||
|
||||
// 获取对应ElevenLabs WebSocket连接,转发语音流(二进制)
|
||||
WebSocket elevenLabsWs = SESSION_MAP.get(vueSession);
|
||||
if (elevenLabsWs != null && elevenLabsWs.queueSize() == 0) {
|
||||
elevenLabsWs.send(ByteString.of(voiceData));
|
||||
System.out.println("转发语音流到ElevenLabs成功");
|
||||
}
|
||||
|
||||
// 释放二进制消息资源
|
||||
// message.isLast();
|
||||
}
|
||||
|
||||
// ==================== 4. 处理Vue前端连接关闭 ====================
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession vueSession, org.springframework.web.socket.CloseStatus status) throws Exception {
|
||||
super.afterConnectionClosed(vueSession, status);
|
||||
System.out.println("Vue前端连接关闭:" + vueSession.getId() + ",原因:" + status.getReason());
|
||||
|
||||
// 关闭对应的ElevenLabs WSS连接
|
||||
WebSocket elevenLabsWs = SESSION_MAP.remove(vueSession);
|
||||
if (elevenLabsWs != null) {
|
||||
elevenLabsWs.close(1000, "Vue客户端断开连接");
|
||||
System.out.println("关闭ElevenLabs WSS连接成功");
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 5. 建立与ElevenLabs Agents的WSS连接 ====================
|
||||
private void buildElevenLabsWssConnection(WebSocketSession vueSession) {
|
||||
// 1. 构建ElevenLabs WSS请求(携带认证头)
|
||||
Request request = new Request.Builder()
|
||||
.url(ELEVEN_LABS_WSS_URL)
|
||||
.header("xi-api-key", ELEVEN_LABS_API_KEY) // 必选认证头
|
||||
.build();
|
||||
|
||||
// 2. 构建ElevenLabs WSS监听器(处理响应并回流到Vue)
|
||||
WebSocketListener elevenLabsListener = new WebSocketListener() {
|
||||
// ElevenLabs WSS连接建立
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
super.onOpen(webSocket, response);
|
||||
System.out.println("与ElevenLabs Agents WSS连接成功");
|
||||
// 存储Vue会话与ElevenLabs WS的映射
|
||||
SESSION_MAP.put(vueSession, webSocket);
|
||||
}
|
||||
|
||||
// 接收ElevenLabs文本消息(如状态、错误提示),回流到Vue
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
super.onMessage(webSocket, text);
|
||||
System.out.println("接收ElevenLabs文本消息:" + text);
|
||||
try {
|
||||
// 回流到Vue前端
|
||||
if (vueSession.isOpen()) {
|
||||
vueSession.sendMessage(new TextMessage(text));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("回流文本消息到Vue失败:" + e.getMessage());
|
||||
throw new RuntimeException("Connect ElevenLabs failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 接收ElevenLabs二进制消息(核心:音频流响应),回流到Vue
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, ByteString bytes) {
|
||||
super.onMessage(webSocket, bytes);
|
||||
byte[] audioData = bytes.toByteArray();
|
||||
System.out.println("接收ElevenLabs音频流数据,字节长度:" + audioData.length);
|
||||
try {
|
||||
// 回流二进制音频流到Vue前端
|
||||
if (vueSession.isOpen()) {
|
||||
vueSession.sendMessage(new BinaryMessage(audioData));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("回流音频流到Vue失败:" + e.getMessage());
|
||||
}
|
||||
public void sendAudio(java.nio.ByteBuffer buffer) {
|
||||
endpoint.sendAudio(buffer);
|
||||
}
|
||||
|
||||
// ElevenLabs WSS连接关闭
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
super.onClosed(webSocket, code, reason);
|
||||
System.out.println("ElevenLabs WSS连接关闭:" + reason + ",状态码:" + code);
|
||||
// 移除映射,关闭Vue连接
|
||||
SESSION_MAP.remove(vueSession);
|
||||
try {
|
||||
if (vueSession.isOpen()) {
|
||||
vueSession.close(org.springframework.web.socket.CloseStatus.NORMAL);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
public void sendText(String text) {
|
||||
endpoint.sendText(text);
|
||||
}
|
||||
|
||||
// ElevenLabs WSS连接异常
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
super.onFailure(webSocket, t, response);
|
||||
System.err.println("ElevenLabs WSS连接异常:" + t.getMessage());
|
||||
// 移除映射,关闭Vue连接
|
||||
SESSION_MAP.remove(vueSession);
|
||||
try {
|
||||
if (vueSession.isOpen()) {
|
||||
vueSession.close(org.springframework.web.socket.CloseStatus.SERVER_ERROR);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
public void close() {
|
||||
endpoint.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 3. 建立ElevenLabs WSS连接
|
||||
OK_HTTP_CLIENT.newWebSocket(request, elevenLabsListener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.vetti.socket.agents;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.socket.BinaryMessage;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import javax.websocket.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Slf4j
|
||||
public class ElevenLabsAgentEndpoint extends Endpoint {
|
||||
|
||||
private Session agentSession;
|
||||
private String traceId;
|
||||
private WebSocketSession frontendSession;
|
||||
|
||||
private final AtomicInteger audioCount = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config) {
|
||||
|
||||
log.info("客户端链接啦:{}", traceId);
|
||||
|
||||
this.agentSession = session;
|
||||
this.traceId = (String) config.getUserProperties().get("traceId");
|
||||
this.frontendSession =
|
||||
(WebSocketSession) config.getUserProperties().get("frontendSession");
|
||||
|
||||
log.info("[traceId={}] ElevenLabs Agent CONNECTED", traceId);
|
||||
|
||||
session.addMessageHandler(String.class, this::onText);
|
||||
session.addMessageHandler(ByteBuffer.class, this::onAudio);
|
||||
}
|
||||
|
||||
private void onText(String message) {
|
||||
try {
|
||||
log.info("[traceId={}] Agent → TEXT {}", traceId, message);
|
||||
frontendSession.sendMessage(new TextMessage(message));
|
||||
} catch (IOException e) {
|
||||
log.error("[traceId={}] Send text failed", traceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void onAudio(ByteBuffer buffer) {
|
||||
try {
|
||||
int count = audioCount.incrementAndGet();
|
||||
if (count == 1) {
|
||||
log.info(
|
||||
"[traceId={}] Agent → AUDIO FIRST packet size={} bytes",
|
||||
traceId,
|
||||
buffer.remaining()
|
||||
);
|
||||
}
|
||||
frontendSession.sendMessage(new BinaryMessage(buffer));
|
||||
} catch (IOException e) {
|
||||
log.error("[traceId={}] Send audio failed", traceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Session session, CloseReason closeReason) {
|
||||
log.info("[traceId={}] ElevenLabs Agent CLOSED {}", traceId, closeReason);
|
||||
}
|
||||
|
||||
public void sendAudio(ByteBuffer buffer) {
|
||||
if (agentSession != null && agentSession.isOpen()) {
|
||||
agentSession.getAsyncRemote().sendBinary(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendText(String text) {
|
||||
if (agentSession != null && agentSession.isOpen()) {
|
||||
agentSession.getAsyncRemote().sendText(text);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
if (agentSession != null) {
|
||||
agentSession.close();
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.vetti.socket.agents;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Data
|
||||
public class ElevenLabsConfig {
|
||||
|
||||
@Value("${elevenLabs.agent-id}")
|
||||
private String agentId;
|
||||
|
||||
@Value("${elevenLabs.api-key}")
|
||||
private String apiKey;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.vetti.socket.agents;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.socket.*;
|
||||
import org.springframework.web.socket.handler.BinaryWebSocketHandler;
|
||||
|
||||
@Slf4j
|
||||
public class FrontendWebSocketHandler extends BinaryWebSocketHandler {
|
||||
|
||||
private ElevenLabsAgentClient agentClient;
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
String traceId = session.getId();
|
||||
log.info("[traceId={}] Vue WebSocket CONNECTED", traceId);
|
||||
|
||||
agentClient = new ElevenLabsAgentClient(traceId, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
|
||||
agentClient.sendAudio(message.getPayload());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
|
||||
agentClient.sendText(message.getPayload());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
log.info("[traceId={}] Vue WebSocket CLOSED", session.getId());
|
||||
agentClient.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.vetti.socket.agents;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.config.annotation.*;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(new FrontendWebSocketHandler(), "/voice-websocket/elevenLabsAgent/{clientId}")
|
||||
.setAllowedOrigins("*");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.vetti.web.controller.ai;
|
||||
import com.vetti.common.core.controller.BaseController;
|
||||
import com.vetti.common.core.domain.R;
|
||||
import com.vetti.hotake.domain.HotakeInitialScreeningQuestionsInfo;
|
||||
import com.vetti.hotake.domain.HotakeRolesInfo;
|
||||
import com.vetti.hotake.domain.dto.*;
|
||||
import com.vetti.hotake.domain.vo.*;
|
||||
import com.vetti.hotake.service.IHotakeAiCommonToolsService;
|
||||
@@ -160,4 +161,24 @@ public class HotakeAiCommonToolsController extends BaseController {
|
||||
return R.ok(hotakeAiCommonToolsService.getRoleLinkAnalysis(roleLinkAnalysisVo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 招聘信息分析补全
|
||||
*/
|
||||
@ApiOperation("招聘信息分析补全(按照步骤生成)")
|
||||
@PostMapping(value = "/roleInfoAnalysisSetUp")
|
||||
public R<HotakeRolesInfoDto> handleRoleInfoAnalysisSetUp(@RequestBody HotakeRolesInfoDto rolesInfoDto)
|
||||
{
|
||||
return R.ok(hotakeAiCommonToolsService.getRoleInfoAnalysisSetUp(rolesInfoDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* AI面试问题生成
|
||||
*/
|
||||
@ApiOperation("AI面试问题生成")
|
||||
@PostMapping(value = "/aiInterviewQuestions")
|
||||
public R<?> handleAiInterviewQuestions(@RequestBody HotakeRolesInfo rolesInfo)
|
||||
{
|
||||
return R.ok(hotakeAiCommonToolsService.getAiInterviewQuestions(rolesInfo));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -155,6 +155,8 @@ elevenLabs:
|
||||
apiKey: sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116
|
||||
# apiKey: sk_88f5a560e1bbde0e5b8b6b6eb1812163a98bfb98554acbec
|
||||
modelId: eleven_turbo_v2_5
|
||||
agent-id: 9c5cb2f7ba9efb61d0f0eee01427b6e00c6abe92d4754cfb794884ac4d73c79d
|
||||
api-key: sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116
|
||||
|
||||
# 语音转文本
|
||||
whisper:
|
||||
|
||||
@@ -155,6 +155,8 @@ elevenLabs:
|
||||
apiKey: sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116
|
||||
# apiKey: sk_88f5a560e1bbde0e5b8b6b6eb1812163a98bfb98554acbec
|
||||
modelId: eleven_turbo_v2_5
|
||||
agent-id: 9c5cb2f7ba9efb61d0f0eee01427b6e00c6abe92d4754cfb794884ac4d73c79d
|
||||
api-key: sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116
|
||||
|
||||
# 语音转文本
|
||||
whisper:
|
||||
|
||||
@@ -1701,95 +1701,105 @@ public class AiCommonPromptConstants {
|
||||
* @return
|
||||
*/
|
||||
public static String initializationRoleLinkAnalysisPrompt(){
|
||||
String promptStr = "你是一位专业的招聘信息补全专家,专门根据已提取的岗位信息进行AI智能补全,严格遵守业务约束规则生成标准API格式响应。\n" +
|
||||
String promptStr = "You are a professional job posting information completion expert, specializing in AI intelligent completion based on extracted job information, strictly following business constraint rules to generate standard API format responses.\n" +
|
||||
"\n" +
|
||||
"## 核心任务\n" +
|
||||
"根据用户提供的「已提取岗位信息」,生成完整的、符合业务约束规则的标准API格式岗位数据。\n" +
|
||||
"## Core Task\n" +
|
||||
"Generate complete job data that complies with business constraint rules in standard API format based on the \"extracted job information\" provided by the user.\n" +
|
||||
"\n" +
|
||||
"## 输入信息说明\n" +
|
||||
"用户将提供一个JSON对象,包含从招聘页面提取的基础信息,其中:\n" +
|
||||
"- **有值的字段**:表示从页面准确提取的信息,请保留原值\n" +
|
||||
"- **null值字段**:表示页面未找到的信息,需要AI智能补全\n" +
|
||||
"- **非标准格式字段**:需要转换为业务规则要求的标准格式\n" +
|
||||
"## Input Information Description\n" +
|
||||
"The user will provide a JSON object containing basic information extracted from job posting pages, where:\n" +
|
||||
"- **Fields with values**: Represent information accurately extracted from the page, please retain original values\n" +
|
||||
"- **null value fields**: Represent information not found on the page, requiring AI intelligent completion\n" +
|
||||
"- **Non-standard format fields**: Need to be converted to standard format required by business rules\n" +
|
||||
"\n" +
|
||||
"## 信息处理原则\n" +
|
||||
"1. **保留准确信息**:对于已从页面准确提取的信息(如jobTitle、companyName、location),请直接保留原值\n" +
|
||||
"2. **格式标准化**:对于格式不标准的信息,请转换为业务规则要求的标准格式\n" +
|
||||
" - `experience: \"3-5年工作经验\"` → `jobExperience: \"2-3 Years\"`\n" +
|
||||
" - `education: \"本科及以上学历\"` → 标准教育要求格式\n" +
|
||||
" - `skills: [\"Java\", \"Spring\"]` → 规则允许的技能格式\n" +
|
||||
"## Information Processing Principles\n" +
|
||||
"1. **Preserve Accurate Information**: For information accurately extracted from the page (such as jobTitle, companyName, location), please retain the original values directly\n" +
|
||||
"2. **Format Standardization**: For non-standard format information, please convert to standard format required by business rules\n" +
|
||||
" - `experience: \"3-5 years experience\"` → `jobExperience: 4`\n" +
|
||||
" - `education: \"Bachelor's degree or above\"` → Standard education requirements format\n" +
|
||||
" - `skills: [\"Java\", \"Spring\"]` → Skill format allowed by rules\n" +
|
||||
" - `salaryRange: \"15K-25K\"` → `salaryStart: 90000, salaryEnd: 150000`\n" +
|
||||
"3. **智能补全**:对于标记为null的缺失信息,请根据岗位特点和行业标准进行合理补全\n" +
|
||||
"4. **严格合规**:所有字段必须遵守业务约束规则表,禁止生成规则外的值\n" +
|
||||
"3. **Intelligent Completion**: For missing information marked as null, please reasonably complete based on job characteristics and industry standards\n" +
|
||||
"4. **Strict Compliance**: All fields must comply with business constraint rules, and values outside the rules are prohibited\n" +
|
||||
"\n" +
|
||||
"## 业务约束规则表\n" +
|
||||
"| 字段名 | 约束类型 | 业务约束值/规则 |\n" +
|
||||
"|----------------------------|----------------|--------------------------------------------------------------------------------|\n" +
|
||||
"| jobType | 可选值集合 | 仅允许:Full-time/Part-time/Contract/Temporary/Internship |\n" +
|
||||
"| jobLevel | 可选值集合 | 仅允许:Entry/Junior/Mid/Senior/Lead/Manager/Director |\n" +
|
||||
"| jobExperience | 文本输入 | 自由文本描述工作经验要求,如\"3-5年相关工作经验\"、\"2年以上Java开发经验\"等 |\n" +
|
||||
"| locationType | 可选值集合 | 仅允许:On-site/Remote/Hybrid |\n" +
|
||||
"| status | 可选值集合 | 仅允许:pause/archived/open/editing |\n" +
|
||||
"| dataType | 可选值集合 | 仅允许:release/draft/template |\n" +
|
||||
"| salaryStart/salaryEnd | 数值范围+类型 | 类型:数字(BigDecimal);范围:salaryStart≥50000,salaryEnd≤200000,且salaryEnd≥salaryStart |\n" +
|
||||
"| descriptionTone | 可选值集合 | 仅允许:professional/friendly/innovative/dynamic(四选一,数组格式) |\n" +
|
||||
"| aboutRole | 文本输入 | 关于职位的详细描述,包含岗位背景、重要性、发展前景等 |\n" +
|
||||
"| responsibilities | 文本输入 | 详细的岗位职责描述,包含主要工作内容和具体任务 |\n" +
|
||||
"| roleBenefitsList | 结构+取值规则 | 1. 结构:数组,每个元素仅含 keyValue 字段;<br>2. 取值:自由文本,如\"五险一金\"、\"年终奖金\"、\"带薪年假\"等;<br>3. 数量:3-8项 |\n" +
|
||||
"| requiredSkillsList | 结构+取值规则 | 1. 结构:数组,每个元素仅含 keyValue 字段;<br>2. 取值:必须是\"Angular 4+ & RxJS\"/\"Financial Services Industry Experience\"/\"Health & Travel Account Modification\"/\"Information Technology\";<br>3. 数量:1-5项 |\n" +
|
||||
"| niceToHaveSkillsList | 结构+取值规则 | 1. 结构:数组,每个元素仅含 keyValue 字段;<br>2. 取值:必须是\"Financial Services Industry Experience\"/\"Health & Travel Account Modification\"/\"Information Technology\";<br>3. 数量:0-3项 |\n" +
|
||||
"| educationRequirements | 结构+可选值 | 1. 结构:对象,含 academicMajor、degree;<br>2. academicMajor:仅允许\"Computer Science\";<br>3. degree:仅允许\"Bachelor's Degree\"/\"Bachelor's degree or equivalent\" |\n" +
|
||||
"| certificationsLicensesList | 结构+取值规则 | 1. 结构:数组,每个元素含 type、val;<br>2. type:仅允许\"standard\"/\"customize\";<br>3. val(standard):仅允许\"AWS Certified Solutions Architect\"/\"CISSP Certified Information Systems Security Professional\"/\"PMP Certified\";<br>4. 数量:0-3项 |\n" +
|
||||
"## Business Constraint Rules Table\n" +
|
||||
"| Field Name | Constraint Type | Business Constraint Values/Rules |\n" +
|
||||
"|----------------------------|-------------------|--------------------------------------------------------------------------------|\n" +
|
||||
"| jobType | Optional Value Set | Only allow: Full-time/Part-time/Contract/Temporary/Internship |\n" +
|
||||
"| jobLevel | Optional Value Set | Only allow: Entry/Junior/Mid/Senior/Lead/Manager/Director |\n" +
|
||||
"| jobExperience | Integer | Work experience years required, integer value 1-10, e.g., 1 for 1 year, 5 for 5 years, 10 for 10+ years |\n" +
|
||||
"\n" +
|
||||
"## 核心要求\n" +
|
||||
"1. 所有字段必须遵守「业务约束规则表」,禁止生成规则外的值;\n" +
|
||||
"2. 数值字段必须匹配类型(如salaryStart为数字,非字符串);\n" +
|
||||
"3. 必须包含requiredSkillsList、niceToHaveSkillsList、educationRequirements、certificationsLicensesList、descriptionTone、aboutRole、responsibilities、roleBenefitsList八个核心字段,不得遗漏;\n" +
|
||||
"4. 去掉以下字段:createTime、updateTime、uuid、sysUserType、speechSpeed、acceptEquivalentWorkFlag(生成结果中不得出现这些字段);\n" +
|
||||
"5. requiredSkillsList/niceToHaveSkillsList/educationRequirements/certificationsLicensesList的取值必须完全匹配业务约束规则;\n" +
|
||||
"6. jobExperience字段为自由文本输入,不限制于预定义选项;\n" +
|
||||
"7. descriptionTone必须从四个选项中选择一个,以数组格式返回;\n" +
|
||||
"8. aboutRole和responsibilities为文本字段,需要详细描述;\n" +
|
||||
"9. roleBenefitsList为数组格式,包含3-8项岗位福利。\n" +
|
||||
"**jobExperience Conversion Rules**:\n" +
|
||||
"- \"0-1 years\" or \"Fresh graduate\" → 1\n" +
|
||||
"- \"1-2 years\" → 2\n" +
|
||||
"- \"2-3 years\" → 3 \n" +
|
||||
"- \"3-5 years\" → 4\n" +
|
||||
"- \"5-8 years\" → 6\n" +
|
||||
"- \"8-10 years\" → 8\n" +
|
||||
"- \"10+ years\" or \"Senior level\" → 10\n" +
|
||||
"- If description is vague, infer from job level: Entry→1-2, Junior→2-3, Mid→3-5, Senior→5-8, Lead/Manager→8-10\n" +
|
||||
"| locationType | Optional Value Set | Only allow: On-site/Remote/Hybrid |\n" +
|
||||
"| status | Optional Value Set | Only allow: pause/archived/open/editing |\n" +
|
||||
"| dataType | Optional Value Set | Only allow: release/draft/template |\n" +
|
||||
"| salaryStart/salaryEnd | Numeric Range+Type | Type: Number (BigDecimal); Range: salaryStart≥50000, salaryEnd≤200000, and salaryEnd≥salaryStart |\n" +
|
||||
"| descriptionTone | Optional Value Set | Only allow: professional/friendly/innovative/dynamic (choose one, array format) |\n" +
|
||||
"| aboutRole | Text Input | Detailed description about the role, including background, importance, and development prospects |\n" +
|
||||
"| responsibilities | Text Input | Detailed job responsibilities description, including main work content and specific tasks |\n" +
|
||||
"| roleBenefitsList | Structure+Value Rules | 1. Structure: Array, each element contains only keyValue field;<br>2. Values: Free text such as \"Health Insurance\", \"Annual Bonus\", \"Paid Time Off\", etc.;<br>3. Quantity: 3-8 items |\n" +
|
||||
"| requiredSkillsList | Structure+Value Rules | 1. Structure: Array, each element contains only keyValue field;<br>2. Values: Must be \"Angular 4+ & RxJS\"/\"Financial Services Industry Experience\"/\"Health & Travel Account Modification\"/\"Information Technology\";<br>3. Quantity: 1-5 items |\n" +
|
||||
"| niceToHaveSkillsList | Structure+Value Rules | 1. Structure: Array, each element contains only keyValue field;<br>2. Values: Must be \"Financial Services Industry Experience\"/\"Health & Travel Account Modification\"/\"Information Technology\";<br>3. Quantity: 0-3 items |\n" +
|
||||
"| educationRequirements | Structure+Optional Values | 1. Structure: Object containing academicMajor, degree;<br>2. academicMajor: Only allow \"Computer Science\";<br>3. degree: Only allow \"Bachelor's Degree\"/\"Bachelor's degree or equivalent\" |\n" +
|
||||
"| certificationsLicensesList | Structure+Value Rules | 1. Structure: Array, each element contains type, val;<br>2. type: Only allow \"standard\"/\"customize\";<br>3. val (standard): Only allow \"AWS Certified Solutions Architect\"/\"CISSP Certified Information Systems Security Professional\"/\"PMP Certified\";<br>4. Quantity: 0-3 items |\n" +
|
||||
"\n" +
|
||||
"## API响应格式要求\n" +
|
||||
"- 顶层字段:code(固定0)、data(HotakeRolesInfoDtoRes)、message(空字符串)、timestamp(当前时间,格式YYYY-MM-DDTHH:MM:SS);\n" +
|
||||
"- 严格排除:createTime、updateTime、uuid、sysUserType、speechSpeed、acceptEquivalentWorkFlag;\n" +
|
||||
"- 严格匹配字段类型(如数组字段不得为单个值,数字字段不得为字符串)。\n" +
|
||||
"## Core Requirements\n" +
|
||||
"1. All fields must comply with the \"Business Constraint Rules Table\", and values outside the rules are prohibited;\n" +
|
||||
"2. Numeric fields must match the type (e.g., salaryStart is a number, not a string);\n" +
|
||||
"3. Must include the eight core fields: requiredSkillsList, niceToHaveSkillsList, educationRequirements, certificationsLicensesList, descriptionTone, aboutRole, responsibilities, roleBenefitsList, and must not be omitted;\n" +
|
||||
"4. Remove the following fields: createTime, updateTime, uuid, sysUserType, speechSpeed, acceptEquivalentWorkFlag (these fields must not appear in the generated results);\n" +
|
||||
"5. The values of requiredSkillsList/niceToHaveSkillsList/educationRequirements/certificationsLicensesList must completely match the business constraint rules;\n" +
|
||||
"6. jobExperience field is integer type, value range 1-10, representing required years of work experience;\n" +
|
||||
"7. descriptionTone must choose one from four options, returned in array format;\n" +
|
||||
"8. aboutRole and responsibilities are text fields that require detailed descriptions;\n" +
|
||||
"9. roleBenefitsList is in array format, containing 3-8 role benefits.\n" +
|
||||
"\n" +
|
||||
"## 输出格式\n" +
|
||||
"请以JSON格式返回补全结果,确保:\n" +
|
||||
"1. 结构完整,包含所有必需字段\n" +
|
||||
"2. 数据类型正确(数字、字符串、数组、对象)\n" +
|
||||
"3. 取值符合业务约束规则\n" +
|
||||
"4. 不包含禁止字段\n" +
|
||||
"## API Response Format Requirements\n" +
|
||||
"- Top-level fields: code (fixed 0), data (HotakeRolesInfoDtoRes), message (empty string), timestamp (current time, format YYYY-MM-DDTHH:MM:SS);\n" +
|
||||
"- Strictly exclude: createTime, updateTime, uuid, sysUserType, speechSpeed, acceptEquivalentWorkFlag;\n" +
|
||||
"- Strictly match field types (e.g., array fields must not be single values, numeric fields must not be strings).\n" +
|
||||
"\n" +
|
||||
"## 示例输出格式\n" +
|
||||
"## Output Format\n" +
|
||||
"Please return the completion results in JSON format, ensuring:\n" +
|
||||
"1. Complete structure with all required fields\n" +
|
||||
"2. Correct data types (numbers, strings, arrays, objects)\n" +
|
||||
"3. Values comply with business constraint rules\n" +
|
||||
"4. Does not contain prohibited fields\n" +
|
||||
"\n" +
|
||||
"## Example Output Format\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 0,\n" +
|
||||
" \"data\": {\n" +
|
||||
" \"jobTitle\": \"岗位名称\",\n" +
|
||||
" \"companyName\": \"公司名称\",\n" +
|
||||
" \"location\": \"工作地点\",\n" +
|
||||
" \"jobTitle\": \"Job Title\",\n" +
|
||||
" \"companyName\": \"Company Name\",\n" +
|
||||
" \"location\": \"Work Location\",\n" +
|
||||
" \"salaryStart\": 80000,\n" +
|
||||
" \"salaryEnd\": 120000,\n" +
|
||||
" \"jobType\": \"Full-time\",\n" +
|
||||
" \"jobLevel\": \"Senior\",\n" +
|
||||
" \"jobExperience\": \"3-5年相关工作经验,熟悉Java开发\",\n" +
|
||||
" \"jobExperience\": 4,\n" +
|
||||
" \"locationType\": \"On-site\",\n" +
|
||||
" \"status\": \"open\",\n" +
|
||||
" \"dataType\": \"release\",\n" +
|
||||
" \"descriptionTone\": [\"professional\"],\n" +
|
||||
" \"aboutRole\": \"这是一个关键的技术岗位,负责公司核心业务系统的开发和维护。该职位将直接参与产品架构设计,是技术团队的重要组成部分,具有良好的职业发展前景。\",\n" +
|
||||
" \"responsibilities\": \"1. 负责后端系统的设计、开发和维护;2. 参与系统架构设计和技术方案制定;3. 编写高质量、可维护的代码;4. 与前端团队协作完成产品功能;5. 参与代码审查和技术分享;6. 解决系统性能和稳定性问题。\",\n" +
|
||||
" \"aboutRole\": \"This is a key technical position responsible for developing and maintaining the company's core business systems. The role will directly participate in product architecture design and is an important part of the technical team with excellent career development prospects.\",\n" +
|
||||
" \"responsibilities\": \"1. Design, develop and maintain backend systems; 2. Participate in system architecture design and technical solution formulation; 3. Write high-quality, maintainable code; 4. Collaborate with frontend teams to complete product features; 5. Participate in code reviews and technical sharing; 6. Solve system performance and stability issues.\",\n" +
|
||||
" \"roleBenefitsList\": [\n" +
|
||||
" {\"keyValue\": \"五险一金\"},\n" +
|
||||
" {\"keyValue\": \"年终奖金\"},\n" +
|
||||
" {\"keyValue\": \"带薪年假\"},\n" +
|
||||
" {\"keyValue\": \"弹性工作时间\"},\n" +
|
||||
" {\"keyValue\": \"技能培训\"},\n" +
|
||||
" {\"keyValue\": \"团队建设活动\"}\n" +
|
||||
" {\"keyValue\": \"Health Insurance\"},\n" +
|
||||
" {\"keyValue\": \"Annual Bonus\"},\n" +
|
||||
" {\"keyValue\": \"Paid Time Off\"},\n" +
|
||||
" {\"keyValue\": \"Flexible Working Hours\"},\n" +
|
||||
" {\"keyValue\": \"Skills Training\"},\n" +
|
||||
" {\"keyValue\": \"Team Building Activities\"}\n" +
|
||||
" ],\n" +
|
||||
" \"requiredSkillsList\": [\n" +
|
||||
" {\"keyValue\": \"Information Technology\"},\n" +
|
||||
@@ -1814,12 +1824,255 @@ public class AiCommonPromptConstants {
|
||||
"}\n" +
|
||||
"```\n" +
|
||||
"\n" +
|
||||
"## 质量要求\n" +
|
||||
"1. 信息保留性:准确保留第一阶段提取的有效信息\n" +
|
||||
"2. 补全合理性:AI补全的信息应符合行业标准和岗位特点\n" +
|
||||
"3. 规则严格性:100%遵守业务约束规则,不得有任何例外\n" +
|
||||
"4. 格式标准性:严格按照API格式要求输出\n" +
|
||||
"5. 完整性验证:确保所有必需字段都已包含";
|
||||
"## Quality Requirements\n" +
|
||||
"1. Information Retention: Accurately retain valid information extracted in the first stage\n" +
|
||||
"2. Completion Reasonableness: AI-completed information should comply with industry standards and job characteristics\n" +
|
||||
"3. Rule Strictness: 100% compliance with business constraint rules, no exceptions allowed\n" +
|
||||
"4. Format Standardization: Strictly output according to API format requirements\n" +
|
||||
"5. Completeness Verification: Ensure all required fields are included";
|
||||
|
||||
return promptStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化AI生成问题提示词
|
||||
* @return
|
||||
*/
|
||||
public static String initializationAiInterviewQuestionsPrompt(){
|
||||
String promptStr = "You are a senior AI interviewer and HR expert, specializing in designing high-quality standardized interview questions for AI voice interview systems.\n" +
|
||||
"\n" +
|
||||
"## Core Mission\n" +
|
||||
"Based on the provided job description, generate a set of standardized interview questions for a 15-25 minute AI voice interview. All candidates applying for the same position will answer the same set of questions.\n" +
|
||||
"\n" +
|
||||
"## AI Interview Characteristics\n" +
|
||||
"\n" +
|
||||
"### 1. Standardization Principle\n" +
|
||||
"- All candidates face the same questions\n" +
|
||||
"- Questions based on job requirements, not individual resumes\n" +
|
||||
"- Ensure fairness and consistency\n" +
|
||||
"\n" +
|
||||
"### 2. Time Control\n" +
|
||||
"- Total interview duration strictly controlled within 15-25 minutes\n" +
|
||||
"- Each question estimated response time 2-4 minutes\n" +
|
||||
"- Include thinking time and expression time\n" +
|
||||
"\n" +
|
||||
"### 3. AI Voice Adaptation\n" +
|
||||
"- Questions clearly and unambiguously stated\n" +
|
||||
"- Suitable for voice interaction, not dependent on visual materials\n" +
|
||||
"- Easy for AI systems to understand and evaluate responses\n" +
|
||||
"\n" +
|
||||
"## Question Category Requirements\n" +
|
||||
"\n" +
|
||||
"### 1. Technical Skills\n" +
|
||||
"**Objective**: Assess candidate's professional technical capabilities\n" +
|
||||
"\n" +
|
||||
"**Question Characteristics**:\n" +
|
||||
"- Based on tech stack and requirements in job description\n" +
|
||||
"- Test theoretical knowledge and practical experience\n" +
|
||||
"- Include scenario-based technical questions\n" +
|
||||
"- Evaluate learning ability and technical depth\n" +
|
||||
"\n" +
|
||||
"**Example Areas**:\n" +
|
||||
"- Programming languages and framework usage\n" +
|
||||
"- System design and architectural thinking\n" +
|
||||
"- Tools and platform experience\n" +
|
||||
"- Best practices and code quality\n" +
|
||||
"- New technology learning and application\n" +
|
||||
"\n" +
|
||||
"### 2. Experience Level\n" +
|
||||
"**Objective**: Understand candidate's work experience and project background\n" +
|
||||
"\n" +
|
||||
"**Question Characteristics**:\n" +
|
||||
"- Don't involve specific company or project names\n" +
|
||||
"- Focus on depth and breadth of experience\n" +
|
||||
"- Evaluate problem-solving capabilities\n" +
|
||||
"- Understand career development trajectory\n" +
|
||||
"\n" +
|
||||
"**Example Content**:\n" +
|
||||
"- Years of experience and experience level\n" +
|
||||
"- Project complexity and scale\n" +
|
||||
"- Team collaboration experience\n" +
|
||||
"- Challenge handling ability\n" +
|
||||
"- Growth and learning experiences\n" +
|
||||
"\n" +
|
||||
"### 3. Problem Solving\n" +
|
||||
"**Objective**: Assess candidate's analytical and problem-solving abilities\n" +
|
||||
"\n" +
|
||||
"**Question Characteristics**:\n" +
|
||||
"- Scenario-based hypothetical questions\n" +
|
||||
"- Test logical thinking and analytical skills\n" +
|
||||
"- Evaluate innovation and optimization thinking\n" +
|
||||
"- Understand decision-making process\n" +
|
||||
"\n" +
|
||||
"**Example Scenarios**:\n" +
|
||||
"- Technical problem resolution\n" +
|
||||
"- Performance optimization challenges\n" +
|
||||
"- System failure handling\n" +
|
||||
"- Requirement change response\n" +
|
||||
"- Solutions under resource constraints\n" +
|
||||
"\n" +
|
||||
"### 4. Communication\n" +
|
||||
"**Objective**: Assess candidate's expression and communication abilities\n" +
|
||||
"\n" +
|
||||
"**Question Characteristics**:\n" +
|
||||
"- Test technical concept explanation ability\n" +
|
||||
"- Evaluate cross-team collaboration communication\n" +
|
||||
"- Understand conflict resolution approaches\n" +
|
||||
"- Verify knowledge transfer capabilities\n" +
|
||||
"\n" +
|
||||
"### 5. Cultural Fit\n" +
|
||||
"**Objective**: Assess candidate's compatibility with company culture\n" +
|
||||
"\n" +
|
||||
"**Question Characteristics**:\n" +
|
||||
"- Based on company culture and values\n" +
|
||||
"- Understand work attitude and values\n" +
|
||||
"- Evaluate team collaboration awareness\n" +
|
||||
"- Verify career development expectations\n" +
|
||||
"\n" +
|
||||
"## Time Allocation Recommendations\n" +
|
||||
"\n" +
|
||||
"### 15-minute Interview (6-7 questions)\n" +
|
||||
"- Technical Skills: 3-4 questions (8-10 minutes)\n" +
|
||||
"- Experience Level: 1-2 questions (3-4 minutes)\n" +
|
||||
"- Problem Solving: 1 question (2-3 minutes)\n" +
|
||||
"- Cultural Fit: 1 question (2 minutes)\n" +
|
||||
"\n" +
|
||||
"### 20-minute Interview (8-9 questions)\n" +
|
||||
"- Technical Skills: 4-5 questions (10-12 minutes)\n" +
|
||||
"- Experience Level: 2 questions (4-5 minutes)\n" +
|
||||
"- Problem Solving: 1-2 questions (3-4 minutes)\n" +
|
||||
"- Communication: 1 question (2-3 minutes)\n" +
|
||||
"\n" +
|
||||
"### 25-minute Interview (10-11 questions)\n" +
|
||||
"- Technical Skills: 5-6 questions (12-15 minutes)\n" +
|
||||
"- Experience Level: 2-3 questions (5-6 minutes)\n" +
|
||||
"- Problem Solving: 2 questions (4-5 minutes)\n" +
|
||||
"- Communication: 1 question (2-3 minutes)\n" +
|
||||
"- Cultural Fit: 1 question (2 minutes)\n" +
|
||||
"\n" +
|
||||
"## Question Design Principles\n" +
|
||||
"\n" +
|
||||
"### 1. Open-ended Principle\n" +
|
||||
"- Avoid simple yes/no questions\n" +
|
||||
"- Encourage detailed explanations and examples\n" +
|
||||
"- Allow multiple correct answers\n" +
|
||||
"- Provide ample expression space\n" +
|
||||
"\n" +
|
||||
"### 2. Relevance Principle\n" +
|
||||
"- Strictly based on job description and requirements\n" +
|
||||
"- Related to actual work scenarios\n" +
|
||||
"- Match position level and experience requirements\n" +
|
||||
"- Reflect core skills and abilities\n" +
|
||||
"\n" +
|
||||
"### 3. Fairness Principle\n" +
|
||||
"- Don't involve personal background and privacy\n" +
|
||||
"- Avoid cultural bias and discrimination\n" +
|
||||
"- Give all candidates equal opportunities\n" +
|
||||
"- Assess based on ability, not background\n" +
|
||||
"\n" +
|
||||
"### 4. Assessability Principle\n" +
|
||||
"- Easy for AI systems to analyze and score\n" +
|
||||
"- Have clear evaluation dimensions\n" +
|
||||
"- Quantifiable assessment criteria\n" +
|
||||
"- Support objective comparative analysis\n" +
|
||||
"\n" +
|
||||
"## Output Format Requirements\n" +
|
||||
"\n" +
|
||||
"Please return AI interview questions in JSON format with the following structure:\n" +
|
||||
"\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"interview_questions\": [\n" +
|
||||
" {\n" +
|
||||
" \"question_id\": \"Q001\",\n" +
|
||||
" \"question_text\": \"Please describe your experience with [relevant technology] and how you learned and mastered this technology?\",\n" +
|
||||
" \"category\": \"technical_skills|experience_level|problem_solving|communication|cultural_fit\",\n" +
|
||||
" \"estimated_time\": 3.0,\n" +
|
||||
" \"difficulty_level\": \"basic|intermediate|advanced\",\n" +
|
||||
" \"evaluation_criteria\": \"Assess technical depth, learning ability, practical experience\",\n" +
|
||||
" \"follow_up_hints\": [\"Can ask about specific technical details\", \"Understand challenges encountered and solutions\"],\n" +
|
||||
" \"expected_answer_points\": [\"Technical understanding level\", \"Learning methods\", \"Practical application\", \"Continuous improvement\"],\n" +
|
||||
" \"scoring_weight\": 1-5\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"interview_summary\": {\n" +
|
||||
" \"total_questions\": 8,\n" +
|
||||
" \"estimated_total_time\": 20,\n" +
|
||||
" \"category_distribution\": {\n" +
|
||||
" \"technical_skills\": 4,\n" +
|
||||
" \"experience_level\": 2,\n" +
|
||||
" \"problem_solving\": 1,\n" +
|
||||
" \"cultural_fit\": 1\n" +
|
||||
" },\n" +
|
||||
" \"difficulty_distribution\": {\n" +
|
||||
" \"basic\": 2,\n" +
|
||||
" \"intermediate\": 5,\n" +
|
||||
" \"advanced\": 1\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"interview_guidelines\": {\n" +
|
||||
" \"introduction\": \"Interview opening statement suggestion\",\n" +
|
||||
" \"transition_phrases\": [\"Transition phrase 1\", \"Transition phrase 2\"],\n" +
|
||||
" \"closing_statement\": \"Interview closing statement\",\n" +
|
||||
" \"time_management_tips\": [\"Time control tip 1\", \"Time control tip 2\"]\n" +
|
||||
" },\n" +
|
||||
" \"evaluation_framework\": {\n" +
|
||||
" \"scoring_method\": \"Scoring method description\",\n" +
|
||||
" \"key_indicators\": [\"Key evaluation indicator 1\", \"Key evaluation indicator 2\"],\n" +
|
||||
" \"red_flags\": [\"Red flag to watch for 1\", \"Red flag to watch for 2\"],\n" +
|
||||
" \"positive_signals\": [\"Positive signal 1\", \"Positive signal 2\"]\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"```\n" +
|
||||
"\n" +
|
||||
"## Quality Standards\n" +
|
||||
"\n" +
|
||||
"### 1. Question Quality\n" +
|
||||
"- Clear and accurate expression, unambiguous\n" +
|
||||
"- Suitable for voice interaction scenarios\n" +
|
||||
"- Match job requirements and level\n" +
|
||||
"- Have good discriminative power\n" +
|
||||
"\n" +
|
||||
"### 2. Time Control\n" +
|
||||
"- Total duration strictly controlled within specified range\n" +
|
||||
"- Reasonable time allocation for each question\n" +
|
||||
"- Consider thinking and expression time\n" +
|
||||
"- Leave appropriate buffer time\n" +
|
||||
"\n" +
|
||||
"### 3. Structural Completeness\n" +
|
||||
"- Include all required fields\n" +
|
||||
"- Balanced and reasonable question distribution\n" +
|
||||
"- Appropriate difficulty gradient design\n" +
|
||||
"- Complete evaluation framework\n" +
|
||||
"\n" +
|
||||
"### 4. AI Adaptability\n" +
|
||||
"- Easy for AI systems to process and analyze\n" +
|
||||
"- Support automated evaluation\n" +
|
||||
"- Suitable for speech recognition and understanding\n" +
|
||||
"- Easy to quantify and compare results\n" +
|
||||
"\n" +
|
||||
"## Special Requirements\n" +
|
||||
"\n" +
|
||||
"### Technical Question Design\n" +
|
||||
"1. **Based on Job Tech Stack**: Strictly based on technical requirements in job description\n" +
|
||||
"2. **Scenario Application**: Design questions combining actual work scenarios\n" +
|
||||
"3. **Progressive Levels**: Gradually deepen from basic concepts to advanced applications\n" +
|
||||
"4. **Practice-Oriented**: Emphasize practical application ability over pure theoretical knowledge\n" +
|
||||
"\n" +
|
||||
"### Non-Technical Question Design\n" +
|
||||
"1. **Cultural Fit**: Design based on company culture and values\n" +
|
||||
"2. **Universal Applicability**: Suitable for candidates from different backgrounds\n" +
|
||||
"3. **Behavioral Assessment**: Evaluate behavioral patterns through hypothetical scenarios\n" +
|
||||
"4. **Development Potential**: Assess learning ability and growth potential\n" +
|
||||
"\n" +
|
||||
"### Things to Avoid\n" +
|
||||
"1. Must not involve personal privacy and sensitive information\n" +
|
||||
"2. Avoid questions requiring specific tools or environments\n" +
|
||||
"3. Don't use overly complex or academic expressions\n" +
|
||||
"4. Avoid cultural bias and discriminatory content\n" +
|
||||
"5. Don't design complex algorithm questions requiring long thinking time\n" +
|
||||
"\n" +
|
||||
"Please generate a high-quality, standardized set of AI interview questions based on the provided job information.\n";
|
||||
|
||||
return promptStr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.vetti.common.enums;
|
||||
|
||||
public enum RoleBenefitsEnum {
|
||||
|
||||
health_insurance("health_insurance", "health insurance"),
|
||||
paid_time_off("paid_time_off", "paid time off"),
|
||||
flexible_work_schedule("flexible_work_schedule", "flexible work schedule"),
|
||||
professional_development_budget("professional_development_budget", "professional development budget"),
|
||||
performance_bonuses("performance_bonuses", "performance bonuses"),
|
||||
wellness_program("wellness_program", "wellness program"),
|
||||
stock_options("stock_options", "stock options/esop"),
|
||||
relocation_support("relocation_support", "relocation support"),
|
||||
parental_leave("parental_leave", "parental leave"),
|
||||
team_events_corporate_activities("team_events_corporate_activities", "team events & corporate activities"),
|
||||
;
|
||||
|
||||
private final String code;
|
||||
private final String info;
|
||||
|
||||
RoleBenefitsEnum(String code, String info)
|
||||
{
|
||||
this.code = code;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getInfo()
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
public static RoleBenefitsEnum getByInfo(String code) {
|
||||
RoleBenefitsEnum[] entityEnums = values();
|
||||
for (RoleBenefitsEnum entityEnum : entityEnums) {
|
||||
if (entityEnum.getCode().equals(code)) {
|
||||
return entityEnum;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static RoleBenefitsEnum getByCode(String info) {
|
||||
RoleBenefitsEnum[] entityEnums = values();
|
||||
for (RoleBenefitsEnum entityEnum : entityEnums) {
|
||||
if (entityEnum.getInfo().equals(info.toLowerCase())) {
|
||||
return entityEnum;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import com.vetti.common.constant.Constants;
|
||||
import com.vetti.common.core.text.StrFormatter;
|
||||
@@ -719,4 +721,11 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getObjectStr(Object obj){
|
||||
if(ObjectUtil.isNotEmpty(obj)){
|
||||
return obj.toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ public class SecurityConfig
|
||||
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
|
||||
requests.antMatchers("/login", "/register", "/captchaImage","/aiCommon/**","/voice-websocket/multiple/**",
|
||||
"/voice-websocket/**","/voice-websocket/multiplePcm/**","/verification/email/send","/verification/email/verify","/verification/phone/send",
|
||||
"/forgotPassword","/verification/email/register/send").permitAll()
|
||||
"/forgotPassword","/verification/email/register/send","/voice-websocket/elevenLabsAgent/**").permitAll()
|
||||
// 静态资源,可匿名访问
|
||||
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
||||
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.vetti.hotake.service;
|
||||
|
||||
import com.vetti.hotake.domain.HotakeAiInterviewQuestionsInfo;
|
||||
import com.vetti.hotake.domain.HotakeInitialScreeningQuestionsInfo;
|
||||
import com.vetti.hotake.domain.HotakeRolesInfo;
|
||||
import com.vetti.hotake.domain.dto.*;
|
||||
import com.vetti.hotake.domain.vo.*;
|
||||
|
||||
@@ -111,4 +112,20 @@ public interface IHotakeAiCommonToolsService {
|
||||
public HotakeRolesInfoDto getRoleLinkAnalysis(HotakeRoleLinkAnalysisVo roleLinkAnalysisVo);
|
||||
|
||||
|
||||
/**
|
||||
* 招聘链接信息分析补全(按照步骤生成)
|
||||
* @param rolesInfoDto 岗位信息
|
||||
* @return
|
||||
*/
|
||||
public HotakeRolesInfoDto getRoleInfoAnalysisSetUp(HotakeRolesInfoDto rolesInfoDto);
|
||||
|
||||
|
||||
/**
|
||||
* AI面试问题生成
|
||||
* @param rolesInfo 岗位信息
|
||||
* @return
|
||||
*/
|
||||
public String getAiInterviewQuestions(HotakeRolesInfo rolesInfo);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.vetti.hotake.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.vetti.common.ai.gpt.ChatGPTClient;
|
||||
import com.vetti.common.constant.AiCommonPromptConstants;
|
||||
import com.vetti.common.core.domain.entity.SysUser;
|
||||
import com.vetti.common.core.service.BaseServiceImpl;
|
||||
import com.vetti.common.enums.FillTypeEnum;
|
||||
import com.vetti.common.enums.RoleBenefitsEnum;
|
||||
import com.vetti.common.utils.SecurityUtils;
|
||||
import com.vetti.common.utils.html.ReadHtmlByOkHttp;
|
||||
import com.vetti.hotake.domain.HotakeInitScreQuestionsReplyRecordInfo;
|
||||
@@ -765,7 +768,7 @@ public class HotakeAiCommonToolsServiceImpl extends BaseServiceImpl implements I
|
||||
String resultJson = "{\"jobTitle\":\"Senior Software Engineer\",\"companyName\":\"Tech Innovation Corp\",\"jobType\":null}";
|
||||
log.info("招聘链接信息提取:{}",resultJson);
|
||||
//处理岗位信息补充
|
||||
String userPrompt_1 = "请根据以下提取的岗位信息生成完整的API格式数据:\\n\\n" +resultJson;
|
||||
String userPrompt_1 = "Please generate complete API-formatted data based on the extracted job information below:\\n\\n" +resultJson;
|
||||
List<Map<String, String>> listOne = new LinkedList();
|
||||
Map<String, String> mapEntityOne = new HashMap<>();
|
||||
mapEntityOne.put("role", "system");
|
||||
@@ -798,25 +801,192 @@ public class HotakeAiCommonToolsServiceImpl extends BaseServiceImpl implements I
|
||||
|
||||
hotakeRolesInfoDto.setResponsibilities(dataMap.get("responsibilities").toString());
|
||||
//岗位所需技能信息
|
||||
List<RequiredSkillsDto> requiredSkillsList = (List<RequiredSkillsDto>)dataMap.get("requiredSkills");
|
||||
List<RequiredSkillsDto> requiredSkillsList = (List<RequiredSkillsDto>)dataMap.get("requiredSkillsList");
|
||||
hotakeRolesInfoDto.setRequiredSkillsList(requiredSkillsList);
|
||||
|
||||
hotakeRolesInfoDto.setRequiredSkillsJson(JSONUtil.toJsonStr(requiredSkillsList));
|
||||
|
||||
List<NiceToHaveSkillsDto> niceToHaveSkillsList = (List<NiceToHaveSkillsDto>)dataMap.get("niceToHaveSkillsList");
|
||||
hotakeRolesInfoDto.setNiceToHaveSkillsList(niceToHaveSkillsList);
|
||||
|
||||
List<RoleBenefitsDto> roleBenefitsList = (List<RoleBenefitsDto>)dataMap.get("roleBenefitsList");
|
||||
hotakeRolesInfoDto.setNiceToHaveSkillsJson(JSONUtil.toJsonStr(niceToHaveSkillsList));
|
||||
|
||||
List<Map> roleBenefitsMapList = (List<Map>)dataMap.get("roleBenefitsList");
|
||||
List<RoleBenefitsDto> roleBenefitsList = new ArrayList<>();
|
||||
if(CollectionUtil.isNotEmpty(roleBenefitsMapList)){
|
||||
for(Map dto : roleBenefitsMapList){
|
||||
//福利转换
|
||||
RoleBenefitsDto dto1 = new RoleBenefitsDto();
|
||||
if(ObjectUtil.isNotEmpty(dto.get("keyValue"))){
|
||||
RoleBenefitsEnum benefitsEnum = RoleBenefitsEnum.getByCode(dto.get("keyValue").toString());
|
||||
if(benefitsEnum != null){
|
||||
dto1.setKeyValue(benefitsEnum.getCode());
|
||||
roleBenefitsList.add(dto1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hotakeRolesInfoDto.setRoleBenefitsList(roleBenefitsList);
|
||||
hotakeRolesInfoDto.setRoleBenefitsJson(JSONUtil.toJsonStr(roleBenefitsList));
|
||||
|
||||
|
||||
Map educationRequirementsMap = (Map)dataMap.get("educationRequirements");
|
||||
EducationRequirementsDto educationRequirements = new EducationRequirementsDto();
|
||||
educationRequirements.setAcademicMajor(educationRequirementsMap.get("academicMajor").toString());
|
||||
educationRequirements.setDegree(educationRequirementsMap.get("degree").toString());
|
||||
hotakeRolesInfoDto.setEducationRequirements(educationRequirements);
|
||||
hotakeRolesInfoDto.setEducationRequirementsJson(JSONUtil.toJsonStr(educationRequirements));
|
||||
|
||||
List<CertificationsLicensesDto> certificationsLicensesList = (List<CertificationsLicensesDto>)dataMap.get("certificationsLicensesList");
|
||||
hotakeRolesInfoDto.setCertificationsLicensesList(certificationsLicensesList);
|
||||
|
||||
hotakeRolesInfoDto.setCertificationsLicensesJson(JSONUtil.toJsonStr(certificationsLicensesList));
|
||||
|
||||
fill(FillTypeEnum.INSERT.getCode(),hotakeRolesInfoDto);
|
||||
hotakeRolesInfoMapper.insertHotakeRolesInfo(hotakeRolesInfoDto);
|
||||
|
||||
return hotakeRolesInfoDto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 招聘链接信息分析补全
|
||||
* @param roleLinkAnalysisVo 岗位链接对象
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public HotakeRolesInfoDto getRoleInfoAnalysisSetUp(HotakeRolesInfoDto rolesInfoDto) {
|
||||
|
||||
String prompt = AiCommonPromptConstants.initializationRoleLinkAnalysisPrompt();
|
||||
|
||||
String resultJson = "{\n" +
|
||||
" \"jobTitle\": "+rolesInfoDto.getRoleName()+",\n" +
|
||||
" \"companyName\": "+rolesInfoDto.getCompanyName()+",\n" +
|
||||
" \"location\": "+rolesInfoDto.getLocations()+",\n" +
|
||||
" \"salaryRange\": "+rolesInfoDto.getSalaryStart()+"-"+rolesInfoDto.getSalaryEnd()+",\n" +
|
||||
" \"jobType\": "+rolesInfoDto.getJobType()+",\n" +
|
||||
" \"experience\": "+rolesInfoDto.getJobExperience()+",\n" +
|
||||
" \"education\": "+JSONUtil.toJsonStr(rolesInfoDto.getEducationRequirements())+",\n" +
|
||||
" \"skills\": "+JSONUtil.toJsonStr(rolesInfoDto.getRequiredSkillsList())+",\n" +
|
||||
" \"description\": "+rolesInfoDto.getAboutRole()+"\n" +
|
||||
"}";
|
||||
log.info("招聘链接信息提取:{}",resultJson);
|
||||
//处理岗位信息补充
|
||||
String userPrompt_1 = "Please generate complete API-formatted data based on the extracted job information below:\\n\\n" +resultJson;
|
||||
List<Map<String, String>> listOne = new LinkedList();
|
||||
Map<String, String> mapEntityOne = new HashMap<>();
|
||||
mapEntityOne.put("role", "system");
|
||||
mapEntityOne.put("content",prompt);
|
||||
listOne.add(mapEntityOne);
|
||||
Map<String, String> mapUserEntityOne = new HashMap<>();
|
||||
mapUserEntityOne.put("role", "user");
|
||||
mapUserEntityOne.put("content",userPrompt_1);
|
||||
listOne.add(mapUserEntityOne);
|
||||
String promptJsonOne = JSONUtil.toJsonStr(listOne);
|
||||
String resultStrOne = chatGPTClient.handleAiChat(promptJsonOne,"RLINKAL");
|
||||
String resultJsonOne = resultStrOne.replaceAll("```json","").replaceAll("```","");
|
||||
log.info("招聘信息补全:{}",resultJsonOne);
|
||||
HotakeRolesInfoDto hotakeRolesInfoDto = new HotakeRolesInfoDto();
|
||||
Map resultMap = JSONUtil.toBean(resultJsonOne,Map.class);
|
||||
|
||||
Map dataMap = (Map)resultMap.get("data");
|
||||
hotakeRolesInfoDto.setRoleName(dataMap.get("jobTitle").toString());
|
||||
|
||||
hotakeRolesInfoDto.setCompanyName(dataMap.get("companyName").toString());
|
||||
hotakeRolesInfoDto.setLocations(dataMap.get("location").toString());
|
||||
hotakeRolesInfoDto.setSalaryStart(new BigDecimal(dataMap.get("salaryStart").toString()));
|
||||
hotakeRolesInfoDto.setSalaryEnd(new BigDecimal(dataMap.get("salaryEnd").toString()));
|
||||
hotakeRolesInfoDto.setJobType(dataMap.get("jobType").toString().toLowerCase());
|
||||
hotakeRolesInfoDto.setJobLevel(dataMap.get("jobLevel").toString().toLowerCase());
|
||||
hotakeRolesInfoDto.setJobExperience("5");
|
||||
hotakeRolesInfoDto.setLocationType(dataMap.get("locationType").toString().toLowerCase());
|
||||
|
||||
hotakeRolesInfoDto.setAboutRole(dataMap.get("aboutRole").toString());
|
||||
|
||||
hotakeRolesInfoDto.setResponsibilities(dataMap.get("responsibilities").toString());
|
||||
//岗位所需技能信息
|
||||
List<RequiredSkillsDto> requiredSkillsList = (List<RequiredSkillsDto>)dataMap.get("requiredSkillsList");
|
||||
hotakeRolesInfoDto.setRequiredSkillsList(requiredSkillsList);
|
||||
|
||||
hotakeRolesInfoDto.setRequiredSkillsJson(JSONUtil.toJsonStr(requiredSkillsList));
|
||||
|
||||
List<NiceToHaveSkillsDto> niceToHaveSkillsList = (List<NiceToHaveSkillsDto>)dataMap.get("niceToHaveSkillsList");
|
||||
hotakeRolesInfoDto.setNiceToHaveSkillsList(niceToHaveSkillsList);
|
||||
|
||||
hotakeRolesInfoDto.setNiceToHaveSkillsJson(JSONUtil.toJsonStr(niceToHaveSkillsList));
|
||||
|
||||
List<Map> roleBenefitsMapList = (List<Map>)dataMap.get("roleBenefitsList");
|
||||
List<RoleBenefitsDto> roleBenefitsList = new ArrayList<>();
|
||||
if(CollectionUtil.isNotEmpty(roleBenefitsMapList)){
|
||||
for(Map dto : roleBenefitsMapList){
|
||||
//福利转换
|
||||
RoleBenefitsDto dto1 = new RoleBenefitsDto();
|
||||
if(ObjectUtil.isNotEmpty(dto.get("keyValue"))){
|
||||
RoleBenefitsEnum benefitsEnum = RoleBenefitsEnum.getByCode(dto.get("keyValue").toString());
|
||||
if(benefitsEnum != null){
|
||||
dto1.setKeyValue(benefitsEnum.getCode());
|
||||
roleBenefitsList.add(dto1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hotakeRolesInfoDto.setRoleBenefitsList(roleBenefitsList);
|
||||
hotakeRolesInfoDto.setRoleBenefitsJson(JSONUtil.toJsonStr(roleBenefitsList));
|
||||
|
||||
Map educationRequirementsMap = (Map)dataMap.get("educationRequirements");
|
||||
EducationRequirementsDto educationRequirements = new EducationRequirementsDto();
|
||||
educationRequirements.setAcademicMajor(educationRequirementsMap.get("academicMajor").toString());
|
||||
educationRequirements.setDegree(educationRequirementsMap.get("degree").toString());
|
||||
hotakeRolesInfoDto.setEducationRequirements(educationRequirements);
|
||||
hotakeRolesInfoDto.setEducationRequirementsJson(JSONUtil.toJsonStr(educationRequirements));
|
||||
|
||||
List<CertificationsLicensesDto> certificationsLicensesList = (List<CertificationsLicensesDto>)dataMap.get("certificationsLicensesList");
|
||||
hotakeRolesInfoDto.setCertificationsLicensesList(certificationsLicensesList);
|
||||
|
||||
hotakeRolesInfoDto.setCertificationsLicensesJson(JSONUtil.toJsonStr(certificationsLicensesList));
|
||||
|
||||
return hotakeRolesInfoDto;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AI面试问题生成
|
||||
* @param rolesInfo 岗位信息
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getAiInterviewQuestions(HotakeRolesInfo rolesInfo) {
|
||||
|
||||
String prompt = AiCommonPromptConstants.initializationAiInterviewQuestionsPrompt();
|
||||
|
||||
String resultJson = "Please generate AI interview questions based on the following job description:\n" +
|
||||
"\n" +
|
||||
"**Job Information**:\n" +
|
||||
"职位名称:【】\n" +
|
||||
"技术要求:【】\n" +
|
||||
"经验要求:【】\n" +
|
||||
"面试时长:【】\n" +
|
||||
"公司文化:【】\n" +
|
||||
"特殊要求:【】\n" +
|
||||
"`;";
|
||||
log.info("招聘链接信息提取:{}",resultJson);
|
||||
//处理岗位信息补充
|
||||
String userPrompt_1 = "Please generate complete API-formatted data based on the extracted job information below:\\n\\n" +resultJson;
|
||||
List<Map<String, String>> listOne = new LinkedList();
|
||||
Map<String, String> mapEntityOne = new HashMap<>();
|
||||
mapEntityOne.put("role", "system");
|
||||
mapEntityOne.put("content",prompt);
|
||||
listOne.add(mapEntityOne);
|
||||
Map<String, String> mapUserEntityOne = new HashMap<>();
|
||||
mapUserEntityOne.put("role", "user");
|
||||
mapUserEntityOne.put("content",userPrompt_1);
|
||||
listOne.add(mapUserEntityOne);
|
||||
String promptJsonOne = JSONUtil.toJsonStr(listOne);
|
||||
String resultStrOne = chatGPTClient.handleAiChat(promptJsonOne,"RLINKAL");
|
||||
String resultJsonOne = resultStrOne.replaceAll("```json","").replaceAll("```","");
|
||||
log.info("招聘信息补全:{}",resultJsonOne);
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,7 @@ import com.vetti.common.config.RuoYiConfig;
|
||||
import com.vetti.common.core.service.BaseServiceImpl;
|
||||
import com.vetti.common.enums.FillTypeEnum;
|
||||
import com.vetti.common.enums.MinioBucketNameEnum;
|
||||
import com.vetti.common.utils.HtmlToPdfUtil;
|
||||
import com.vetti.common.utils.MarkdownUtil;
|
||||
import com.vetti.common.utils.MultipartFileUtil;
|
||||
import com.vetti.common.utils.SecurityUtils;
|
||||
import com.vetti.common.utils.*;
|
||||
import com.vetti.common.utils.readFile.FileContentUtil;
|
||||
import com.vetti.hotake.domain.HotakeCvInfo;
|
||||
import com.vetti.hotake.domain.HotakeProblemBaseInfo;
|
||||
@@ -231,7 +228,6 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
||||
//修改的时候重新生成问题和评分
|
||||
|
||||
|
||||
|
||||
hotakeCvInfoMapper.updateHotakeCvInfo(hotakeCvInfo);
|
||||
|
||||
return hotakeCvInfo;
|
||||
@@ -275,6 +271,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
||||
|
||||
/**
|
||||
* 简历解析以及生成问题和评分
|
||||
*
|
||||
* @param hotakeCvInfo 简历信息
|
||||
* @return
|
||||
*/
|
||||
@@ -352,6 +349,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
||||
|
||||
/**
|
||||
* 根据其他的附件信息生成个人简历
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@@ -371,7 +369,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
||||
basicInformation = cvInfos.get(0).getAnalyzedAttachmentJson();
|
||||
}
|
||||
List<HotakeCvInfo> cvInfoOnes = cvInfoList.stream().filter(e -> !"letter".equals(e.getCvFileType())
|
||||
&& "cv".equals(e.getCvFileType())).toList();
|
||||
&& !"cv".equals(e.getCvFileType())).toList();
|
||||
if (CollectionUtil.isNotEmpty(cvInfoOnes)) {
|
||||
List<Map> attachmentContentList = new ArrayList<>();
|
||||
for (HotakeCvInfo hotakeCvInfo : cvInfoOnes) {
|
||||
@@ -391,17 +389,47 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
||||
HotakeCvInfoDto cvInfoDto = new HotakeCvInfoDto();
|
||||
//个人基本信息
|
||||
Map personalInfoMap = (Map) dataMap.get("personal_info");
|
||||
cvInfoDto.setName(personalInfoMap.get("name").toString());
|
||||
if (personalInfoMap != null) {
|
||||
cvInfoDto.setName(StringUtils.getObjectStr(personalInfoMap.get("full_name")));
|
||||
Map contactDetailsMap = (Map) personalInfoMap.get("contact_details");
|
||||
cvInfoDto.setPhone(contactDetailsMap.get("phone").toString());
|
||||
cvInfoDto.setEmail(contactDetailsMap.get("email").toString());
|
||||
cvInfoDto.setLocation(contactDetailsMap.get("address").toString());
|
||||
//个人介绍
|
||||
cvInfoDto.setAbout(dataMap.get("professional_summary").toString());
|
||||
if (contactDetailsMap != null) {
|
||||
cvInfoDto.setPhone(StringUtils.getObjectStr(contactDetailsMap.get("phone")));
|
||||
cvInfoDto.setEmail(StringUtils.getObjectStr(contactDetailsMap.get("email")));
|
||||
cvInfoDto.setLocation(StringUtils.getObjectStr(contactDetailsMap.get("address")));
|
||||
} else {
|
||||
cvInfoDto.setPhone(StringUtils.getObjectStr(personalInfoMap.get("phone")));
|
||||
cvInfoDto.setEmail(StringUtils.getObjectStr(personalInfoMap.get("email")));
|
||||
cvInfoDto.setLocation(StringUtils.getObjectStr(personalInfoMap.get("address")));
|
||||
}
|
||||
}
|
||||
|
||||
//个人介绍
|
||||
cvInfoDto.setAbout(StringUtils.getObjectStr(dataMap.get("professional_summary")));
|
||||
|
||||
try{
|
||||
//技能工具
|
||||
Map skillsCertificatesMap = (Map)dataMap.get("skills_certificates");
|
||||
List<String> skillsList = (List<String>)skillsCertificatesMap.get("skills");
|
||||
List<Map> skillsList = (List<Map>) dataMap.get("skills_certificates");
|
||||
if(CollectionUtil.isNotEmpty(skillsList)){
|
||||
Map map = skillsList.get(0);
|
||||
if(map != null){
|
||||
List<String> skillsAllList = (List<String>) map.get("skills");
|
||||
//技能工具
|
||||
List<VcSkillsToolsDto> skillsTools = new ArrayList<>();
|
||||
if (CollectionUtil.isNotEmpty(skillsAllList)) {
|
||||
for (String skill : skillsAllList) {
|
||||
VcSkillsToolsDto toolsDto = new VcSkillsToolsDto();
|
||||
toolsDto.setContent(skill);
|
||||
skillsTools.add(toolsDto);
|
||||
}
|
||||
}
|
||||
cvInfoDto.setSkillsTools(skillsTools);
|
||||
}
|
||||
|
||||
}
|
||||
}catch (Exception e){
|
||||
//技能工具
|
||||
List<String> skillsList = (List<String>) dataMap.get("skills_certificates");
|
||||
if(CollectionUtil.isNotEmpty(skillsList)){
|
||||
//技能工具
|
||||
List<VcSkillsToolsDto> skillsTools = new ArrayList<>();
|
||||
if (CollectionUtil.isNotEmpty(skillsList)) {
|
||||
@@ -412,6 +440,10 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
||||
}
|
||||
}
|
||||
cvInfoDto.setSkillsTools(skillsTools);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//工作经验集合
|
||||
List<VcExperienceDto> experience = new ArrayList<>();
|
||||
List<Map> workExperienceMapList = (List<Map>) dataMap.get("work_experience");
|
||||
@@ -419,16 +451,13 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
||||
for (Map map : workExperienceMapList) {
|
||||
VcExperienceDto experienceDto = new VcExperienceDto();
|
||||
|
||||
experienceDto.setCompany(map.get("company").toString());
|
||||
experienceDto.setTitle(map.get("position").toString());
|
||||
String time_period = map.get("time_period").toString();
|
||||
if(StrUtil.isNotEmpty(time_period)){
|
||||
String[] times = time_period.split("–");
|
||||
experienceDto.setDurationStart(times[0]);
|
||||
if(times.length > 1){
|
||||
experienceDto.setDurationEnd(times[1]);
|
||||
}
|
||||
}
|
||||
experienceDto.setCompany(StringUtils.getObjectStr(map.get("company")));
|
||||
experienceDto.setTitle(StringUtils.getObjectStr(map.get("job_title")));
|
||||
experienceDto.setLocation(StringUtils.getObjectStr(map.get("location")));
|
||||
String startDate = StringUtils.getObjectStr(map.get("start_date"));
|
||||
experienceDto.setDurationStart(startDate);
|
||||
String endDate = StringUtils.getObjectStr(map.get("end_date"));
|
||||
experienceDto.setDurationEnd(endDate);
|
||||
List<VcExperienceDescriptionDto> description = new ArrayList<>();
|
||||
List<String> responsibilitiesList = (List<String>) map.get("responsibilities");
|
||||
if (CollectionUtil.isNotEmpty(responsibilitiesList)) {
|
||||
@@ -449,9 +478,9 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
||||
if (CollectionUtil.isNotEmpty(educationMapList)) {
|
||||
for (Map map : educationMapList) {
|
||||
VcEducationDto educationDto = new VcEducationDto();
|
||||
educationDto.setDegree(map.get("degree").toString());
|
||||
educationDto.setInstitution(map.get("school").toString());
|
||||
String time_period = map.get("time_period").toString();
|
||||
educationDto.setDegree(StringUtils.getObjectStr(map.get("degree")));
|
||||
educationDto.setInstitution(StringUtils.getObjectStr(map.get("institution")));
|
||||
String time_period = StringUtils.getObjectStr(map.get("graduation_date"));
|
||||
if (StrUtil.isNotEmpty(time_period)) {
|
||||
String[] times = time_period.split("–");
|
||||
educationDto.setDurationStart(times[0]);
|
||||
@@ -508,7 +537,8 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
||||
insertHotakeCvInfo(cvInfo);
|
||||
//保存简历PDF附件数据
|
||||
// log.info("Markdown数据为:{}",markdown);
|
||||
}catch (Exception e){}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return cvInfo;
|
||||
}
|
||||
|
||||
@@ -692,6 +722,7 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
||||
|
||||
/**
|
||||
* 处理分析附件返回对应的json 内容
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String handleAnalyzedAttachment(String fileUrl, String fileSuffix) {
|
||||
@@ -706,12 +737,10 @@ public class HotakeCvInfoServiceImpl extends BaseServiceImpl implements IHotakeC
|
||||
String resultStr = aiCommonToolsService.handleAnalyzedAttachment(contents);
|
||||
log.info("返回的分析附件结果数据为:{}", resultStr);
|
||||
return resultStr;
|
||||
}catch (Exception e){}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user