Agent 业务逻辑完善

This commit is contained in:
2026-01-15 19:58:15 +08:00
parent 85294a917a
commit 914c69c2de
14 changed files with 591 additions and 235 deletions

16
pom.xml
View File

@@ -292,6 +292,13 @@
</exclusions> </exclusions>
</dependency> </dependency>
<!-- OkHttp 日志拦截器(新增,必须与核心包版本一致) -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>${okhttp3.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.sendgrid</groupId> <groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId> <artifactId>sendgrid-java</artifactId>
@@ -373,6 +380,15 @@
<version>2.1.3</version> <version>2.1.3</version>
</dependency> </dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
<version>2.1.3</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@@ -97,6 +97,15 @@
<artifactId>tyrus-client</artifactId> <artifactId>tyrus-client</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
</dependency>
</dependencies> </dependencies>

View File

@@ -1,7 +1,12 @@
package com.vetti.socket; package com.vetti.socket;
import cn.hutool.json.JSONUtil;
import javax.websocket.*; import javax.websocket.*;
import java.net.URI; import java.net.URI;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
// 客户端端点类 // 客户端端点类
@ClientEndpoint @ClientEndpoint
@@ -11,21 +16,51 @@ public class MyWebSocketClient {
@OnOpen @OnOpen
public void onOpen(Session session) { public void onOpen(Session session) {
System.out.println("连接已建立Session ID: " + session.getId()); System.out.println("连接已建立Session ID: " + session.getId());
try {
// 发送消息到服务端
session.getBasicRemote().sendText("Hello, Server!");
} catch (Exception e) {
e.printStackTrace();
}
} }
// 收到服务端消息时触发 // 收到服务端消息时触发
@OnMessage @OnMessage
public void onMessage(String message, Session session) { public void onMessage(String message, Session session) {
System.out.println("收到服务端消息: " + message); System.out.println("收到服务端消息: " + message);
Map map1 = JSONUtil.parseObj(message);
Map data = (Map) map1.get("text_response_part");
if(map1 != null && "conversation_initiation_metadata".equals(map1.get("type").toString())){
try {
// 发送消息到服务端
Map<String,String> map = new HashMap<>();
map.put("type", "contextual_update");
map.put("user_id", session.getId());
map.put("text", "你好,只回复一句话");
String s = JSONUtil.toJsonStr(map);
session.getBasicRemote().sendText(s);
} catch (Exception e) {
e.printStackTrace();
}
}
if(data != null && "stop".equals(data.get("type").toString())&& "agent_chat_response_part".equals(map1.get("type").toString())){
try {
Thread.sleep(2000);
// 发送消息到服务端
Map<String,String> map = new HashMap<>();
map.put("type", "contextual_update");
map.put("user_id", session.getId());
map.put("text", "你能回答我其他的嘛");
String s = JSONUtil.toJsonStr(map);
session.getBasicRemote().sendText(s);
} catch (Exception e) {
e.printStackTrace();
}
}
// 可根据消息内容做后续处理 // 可根据消息内容做后续处理
} }
// @OnMessage
// public void onBinary(ByteBuffer buffer) {
// System.out.println("收到服务端语音流啦: " + buffer);
// }
// 连接关闭时触发 // 连接关闭时触发
@OnClose @OnClose
public void onClose(Session session, CloseReason reason) { public void onClose(Session session, CloseReason reason) {
@@ -41,16 +76,18 @@ public class MyWebSocketClient {
public static void main(String[] args) { public static void main(String[] args) {
// WebSocket服务端地址示例 // WebSocket服务端地址示例
String serverUri = "ws://vetti.hotake.cn/prod-api/voice-websocket-opus/104"; String serverUri = "ws://vetti.hotake.cn/prod-api/voice-websocket/elevenLabsAgent/104";
try { try {
// 获取WebSocket容器 // 获取WebSocket容器
WebSocketContainer container = ContainerProvider.getWebSocketContainer(); WebSocketContainer container = ContainerProvider.getWebSocketContainer();
// 连接服务端传入客户端端点实例和服务端URI // 连接服务端传入客户端端点实例和服务端URI
container.connectToServer(new MyWebSocketClient(), new URI(serverUri)); container.connectToServer(new MyWebSocketClient(), new URI(serverUri));
// // 1. 设置文本消息缓冲区256KB测试足够用
// 阻塞主线程,避免程序退出(实际场景根据需求处理) // container.setDefaultMaxTextMessageBufferSize(2560 * 1024);
Thread.sleep(60000); // // 2. 设置二进制消息缓冲区(语音流用)
// container.setDefaultMaxBinaryMessageBufferSize(2560 * 1024);
// // 阻塞主线程,避免程序退出(实际场景根据需求处理)
Thread.sleep(6000000);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }

View File

@@ -1,80 +0,0 @@
package com.vetti.socket.agents;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.WebSocketSession;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.ContainerProvider;
import javax.websocket.WebSocketContainer;
import java.net.URI;
import java.util.List;
import java.util.Map;
@Slf4j
public class ElevenLabsAgentClient {
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();
config.getUserProperties().put("traceId", traceId);
config.getUserProperties().put("frontendSession", frontendSession);
container.connectToServer(
endpoint,
config,
URI.create(
String.format(
AGENT_WS_URL,
"9c5cb2f7ba9efb61d0f0eee01427b6e00c6abe92d4754cfb794884ac4d73c79d"
)
)
);
} catch (Exception e) {
throw new RuntimeException("Connect ElevenLabs failed", e);
}
}
public void sendAudio(java.nio.ByteBuffer buffer) {
endpoint.sendAudio(buffer);
}
public void sendText(String text) {
endpoint.sendText(text);
}
public void close() {
endpoint.close();
}
}

View File

@@ -1,88 +1,81 @@
package com.vetti.socket.agents; package com.vetti.socket.agents;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j; 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 javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger; import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
/**
* ElevenLabs Agent 客户端端点 - 处理逻辑
*/
@Slf4j @Slf4j
public class ElevenLabsAgentEndpoint extends Endpoint { public class ElevenLabsAgentEndpoint extends Endpoint {
private Session agentSession; private Session session;
private String traceId;
private WebSocketSession frontendSession;
private final AtomicInteger audioCount = new AtomicInteger(); private final Consumer<Object> onMessage;
public ElevenLabsAgentEndpoint(Consumer<Object> onMessage) {
this.onMessage = onMessage;
}
@Override @Override
public void onOpen(Session session, EndpointConfig config) { public void onOpen(Session session, EndpointConfig config) {
log.info("自动链接上了Agent");
log.info("客户端链接啦:{}", traceId); this.session = session;
log.info("可以开始发送消息了");
this.agentSession = session; session.addMessageHandler(String.class, onMessage::accept);
this.traceId = (String) config.getUserProperties().get("traceId"); session.addMessageHandler(ByteBuffer.class, onMessage::accept);
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) { public void sendText(String text) {
if (agentSession != null && agentSession.isOpen()) { log.info("Agent-开始发送文本消息: {}", text);
agentSession.getAsyncRemote().sendText(text); log.info("Agent-发送文本的Session为: {}", session);
if (session == null || !session.isOpen()) return;
log.info("Agent-开始发送文本发送: {}", text);
session.getAsyncRemote().sendText(text);
}
public void sendBinaryText(String text) {
log.info("Agent-开始发送语音文本消息: {}", text);
log.info("Agent-发送语音文本的Session为: {}", session);
if (session == null || !session.isOpen()) return;
log.info("Agent-开始发送语音文本发送: {}", text);
session.getAsyncRemote().sendText(text);
}
public void sendBinary(ByteBuffer buffer) {
if (session == null || !session.isOpen()) return;
session.getAsyncRemote().sendBinary(buffer);
}
public void close() throws IOException {
if (session != null && session.isOpen()) {
session.close();
} }
} }
public void close() { /**
* 针对语音流发送的时候,如果什么都接收不到的时候,就直接进行提交
*/
public void commit() {
if (session == null || !session.isOpen()) return;
try { try {
if (agentSession != null) { log.info("Agent-开发发送提交拉");
agentSession.close(); Map<String,String> map = new HashMap<>();
} map.put("type","input_audio_buffer.commit");
} catch (Exception ignored) {} session.getAsyncRemote().sendText(JSONUtil.toJsonStr(map));
} catch (Exception e) {
e.printStackTrace();
}
} }
} }

View File

@@ -1,17 +0,0 @@
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;
}

View File

@@ -1,36 +0,0 @@
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();
}
}

View File

@@ -0,0 +1,200 @@
package com.vetti.socket.agents;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.vetti.common.config.RuoYiConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Base64;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@Slf4j
@ServerEndpoint("/voice-websocket/elevenLabsAgent/{clientId}")
@Component
public class VoiceBridgeEndpoint {
// 语音文件保存目录
private static final String VOICE_STORAGE_DIR = "/voice_files/";
// 语音结果文件保存目录
private static final String VOICE_STORAGE_RESULT_DIR = "/voice_result_files/";
// 系统语音目录
private static final String VOICE_SYSTEM_DIR = "/system_files/";
private Session frontendSession;
private ElevenLabsAgentEndpoint agentClient;
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture<?> pendingCommit;
private final long timeoutMs = 600;
private static final String AGENT_URL =
"wss://api.elevenlabs.io/v1/convai/conversation" +
"?agent_id=agent_9401kd09yfjnes2vddz1n29wev2t";
@OnOpen
public void onOpen(Session session) throws Exception {
log.info("已经有客户端链接啦:{}",session.getId());
this.frontendSession = session;
ClientEndpointConfig clientConfig =
ClientEndpointConfig.Builder.create()
.configurator(new ClientEndpointConfig.Configurator() {
@Override
public void beforeRequest(
Map<String, List<String>> headers) {
headers.put(
"Authorization",
List.of("Bearer "+"sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116" )
);
}
})
.build();
WebSocketContainer container =
ContainerProvider.getWebSocketContainer();
agentClient = new ElevenLabsAgentEndpoint(msg -> {
if (msg instanceof String) {
frontendSession.getAsyncRemote()
.sendText((String) msg);
} else if (msg instanceof ByteBuffer) {
frontendSession.getAsyncRemote()
.sendBinary((ByteBuffer) msg);
}
});
// ✅ 完全匹配的方法签名
container.connectToServer(
agentClient, // Endpoint 子类
clientConfig,
URI.create(AGENT_URL)
);
log.info("我开始准备发送启动的提示语音啦");
//链接成功啦
//发送初始化面试官语音流
// String openingPathUrl = RuoYiConfig.getProfile() + VOICE_SYSTEM_DIR + "opening.wav";
// sendVoiceBuffer(openingPathUrl, session);
log.info("发送完毕啦");
}
/** 前端 → ElevenLabsJSON 控制) */
@OnMessage
public void onText(String message) {
log.info("我收到前端发送过来的文本啦:{}",message);
agentClient.sendText(message);
}
/** 前端 → ElevenLabs语音 PCM Binary */
@OnMessage
public void onBinary(ByteBuffer buffer) {
log.info("我收到前端发送过来的PCM语音流啦");
//处理语音流,base64推送过去
String bufferBase64 = convertByteBufferToBase64Pcm16k(buffer);
Map<String,String> binaryMap = new HashMap<>();
binaryMap.put("type","input_audio_buffer.append");
binaryMap.put("audio",bufferBase64);
String jsonStr = JSONUtil.toJsonStr(binaryMap);
log.info("记录Agent对象是不是为空:{}",jsonStr);
agentClient.sendBinaryText(jsonStr);
//发送结束的语音流-进行语音流提交
// 重置 commit 定时器
if (pendingCommit != null) {
pendingCommit.cancel(false);
}
pendingCommit = scheduler.schedule(() -> {
log.info("No audio received, commit()");
agentClient.commit();
}, timeoutMs, TimeUnit.MILLISECONDS);
}
@OnClose
public void onClose() throws IOException {
agentClient.close();
}
@OnError
public void onError(Throwable t) {
t.printStackTrace();
}
/**
* 发送语音流给前端
*
* @param pathUrl 语音文件地址
* @param session 客户端会话
*/
private void sendVoiceBuffer(String pathUrl, Session session) {
try {
//文件转换成文件流
ByteBuffer outByteBuffer = convertFileToByteBuffer(pathUrl);
//发送文件流数据
session.getAsyncRemote().sendBinary(outByteBuffer);
try {
Thread.sleep(200);
}catch (Exception e){}
//提示已经结束
Map<String,String> dataText = new HashMap<>();
dataText.put("type","voiceEnd");
dataText.put("content","");
session.getAsyncRemote().sendText(JSONUtil.toJsonStr(dataText));
// 发送响应确认
log.info("已经成功发送了语音流给前端:{}", DateUtil.now());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* File 转换成 ByteBuffer
*
* @param fileUrl 文件路径
* @return
*/
private ByteBuffer convertFileToByteBuffer(String fileUrl) {
File file = new File(fileUrl);
try {
return ByteBuffer.wrap(FileUtils.readFileToByteArray(file));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 核心方法ByteBuffer 转 Base64 编码的 16K PCM
* @param buffer 原始16K PCM音频的ByteBuffer
* @return Base64字符串16K PCM格式
*/
private String convertByteBufferToBase64Pcm16k(ByteBuffer buffer) {
// 1. 从ByteBuffer提取字节数组关键避免越界
byte[] audioBytes = new byte[buffer.remaining()];
buffer.get(audioBytes); // 读取数据到字节数组buffer指针会移动
buffer.rewind(); // 重置buffer指针可选便于后续复用
// 2. 编码为Base64字符串Java 8+ 原生支持)
return Base64.getEncoder().encodeToString(audioBytes);
}
}

View File

@@ -1,16 +0,0 @@
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("*");
}
}

View File

@@ -0,0 +1,25 @@
package com.vetti.socket.agents.ai;
public class AgentTest {
public static void main(String[] args) throws Exception {
String agentId = "agent_9401kd09yfjnes2vddz1n29wev2t";
String apiKey = "sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116";
ElevenLabsAgentEndpoint client = new ElevenLabsAgentEndpoint();
client.connect(agentId, apiKey);
// 发送第一条消息
client.sendTextMessage("你好,只回复一句话");
// 可以睡几秒,等待 Agent 回复
Thread.sleep(3000);
// 发送第二条消息
client.sendTextMessage("再来一句自我介绍");
Thread.sleep(5000);
client.close();
}
}

View File

@@ -0,0 +1,89 @@
package com.vetti.socket.agents.ai;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.websocket.*;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class ElevenLabsAgentEndpoint extends Endpoint {
private Session session;
private final ObjectMapper mapper = new ObjectMapper();
@Override
public void onOpen(Session session, EndpointConfig config) {
this.session = session;
System.out.println("WebSocket opened");
session.addMessageHandler(String.class, this::onMessage);
}
@Override
public void onClose(Session session, CloseReason closeReason) {
System.out.println("WebSocket closed: " + closeReason);
}
@Override
public void onError(Session session, Throwable thr) {
thr.printStackTrace();
}
private void onMessage(String message) {
try {
JsonNode json = mapper.readTree(message);
System.out.println("Received: " + json.toPrettyString());
// 判断 turn 结束
if ("agent_chat_response_part".equals(json.path("type").asText())) {
JsonNode part = json.path("text_response_part");
if ("stop".equals(part.path("type").asText())) {
System.out.println("Turn ended");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void sendTextMessage(String text) {
if (session != null && session.isOpen()) {
String msgJson = String.format("{\"type\":\"input_text\",\"text\":\"%s\"}", text);
session.getAsyncRemote().sendText(msgJson);
} else {
throw new IllegalStateException("Session not open");
}
}
public void connect(String agentId, String apiKey) throws Exception {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
// 配置 HTTP header
ClientEndpointConfig.Configurator configurator = new ClientEndpointConfig.Configurator() {
@Override
public void beforeRequest(Map<String, List<String>> headers) {
headers.put("xi-api-key", Collections.singletonList(apiKey));
}
};
ClientEndpointConfig config = ClientEndpointConfig.Builder.create()
.configurator(configurator)
.build();
String url = "wss://api.elevenlabs.io/v1/convai/conversation?agent_id=" + agentId;
// 注意,这里是关键:返回 Session
Session wsSession = container.connectToServer(this, config, URI.create(url));
this.session = wsSession;
}
public void close() throws Exception {
if (session != null) {
session.close();
}
}
}

View File

@@ -0,0 +1,4 @@
package com.vetti.web.service;
public interface ElevenLabsConvAiTokenClientService {
}

View File

@@ -0,0 +1,133 @@
package com.vetti.web.service.impl;
import com.google.gson.Gson;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class ElevenLabsConvAiTokenClientServiceImpl {
// ========== 配置项(替换为你的实际值) ==========
private static final String XI_API_KEY = "sk_dfe2b45e19bf8ad93a71d3a0faa61619a91e817df549d116"; // 从ElevenLabs控制台获取
private static final String AGENT_ID = "agent_9401kd09yfjnes2vddz1n29wev2t"; // 如agent_9401kd09yfjnes2vddz1n29wev2t
private static final String TOKEN_API_URL = "https://api.elevenlabs.io/v1/convai/conversation/token";
// JSON媒体类型
private static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8");
private final OkHttpClient client;
private final Gson gson;
// 构造方法:初始化客户端
public ElevenLabsConvAiTokenClientServiceImpl() {
// 初始化OkHttp客户端设置超时
this.client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
this.gson = new Gson();
}
// ===================== 核心获取ConvAI Token =====================
/**
* 获取ConvAI对话Token
* @param conversationId 会话ID可选不传则自动生成
* @param userId 用户ID可选用于区分用户
* @return Token响应对象
* @throws IOException 网络/接口异常
*/
public ConvAiTokenResponse getConvAiToken(String conversationId, String userId) throws IOException {
// 1. 构造请求体
ConvAiTokenRequest requestBody = new ConvAiTokenRequest();
requestBody.setAgent_id(AGENT_ID);
// 会话ID不传则生成随机ID
requestBody.setConversation_id(conversationId == null ? UUID.randomUUID().toString() : conversationId);
requestBody.setUser_id(userId); // 用户ID可选
// 2. 构建HTTP请求
Request request = new Request.Builder()
.url(TOKEN_API_URL)
// 核心鉴权添加XI-API-KEY头
.addHeader("xi-api-key", XI_API_KEY)
.addHeader("Content-Type", "application/json")
// 发送JSON请求体
.post(RequestBody.create(gson.toJson(requestBody), JSON_MEDIA_TYPE))
.build();
// 3. 执行请求并解析响应
try (Response response = client.newCall(request).execute()) {
// 检查响应状态
if (!response.isSuccessful()) {
String errorBody = response.body() != null ? response.body().string() : "无错误信息";
throw new IOException("获取Token失败HTTP码=" + response.code() + ",错误信息=" + errorBody);
}
// 解析JSON响应为实体类
String responseBody = response.body().string();
return gson.fromJson(responseBody, ConvAiTokenResponse.class);
}
}
// ===================== 数据模型(匹配接口格式) =====================
/**
* Token请求体对应接口入参
*/
static class ConvAiTokenRequest {
private String agent_id; // Agent ID必填
private String conversation_id; // 会话ID可选
private String user_id; // 用户ID可选
// Getter & Setter
public String getAgent_id() { return agent_id; }
public void setAgent_id(String agent_id) { this.agent_id = agent_id; }
public String getConversation_id() { return conversation_id; }
public void setConversation_id(String conversation_id) { this.conversation_id = conversation_id; }
public String getUser_id() { return user_id; }
public void setUser_id(String user_id) { this.user_id = user_id; }
}
/**
* Token响应体对应接口返回
*/
static class ConvAiTokenResponse {
private String token; // 核心对话Token
private String conversation_id; // 会话ID
private long expires_at; // Token过期时间时间戳
// Getter & Setter
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public String getConversation_id() { return conversation_id; }
public void setConversation_id(String conversation_id) { this.conversation_id = conversation_id; }
public long getExpires_at() { return expires_at; }
public void setExpires_at(long expires_at) { this.expires_at = expires_at; }
}
// ===================== 测试主方法 =====================
// public static void main(String[] args) {
// ElevenLabsConvAiTokenClientServiceImpl client = new ElevenLabsConvAiTokenClientServiceImpl();
// try {
// // 获取Token传用户ID会话ID自动生成
// ConvAiTokenResponse response = client.getConvAiToken(null, "test_user_001");
//
// // 打印结果
// System.out.println("✅ 获取Token成功");
// System.out.println("Token" + response.getToken());
// System.out.println("会话ID" + response.getConversation_id());
// System.out.println("过期时间(时间戳):" + response.getExpires_at());
//
// // 后续使用拼接WebSocket地址
// String wsUrl = "wss://api.elevenlabs.io/v1/convai/ws?token=" + response.getToken();
// System.out.println("WebSocket连接地址" + wsUrl);
//
// } catch (IOException e) {
// System.err.println("❌ 获取Token失败" + e.getMessage());
// e.printStackTrace();
// }
// }
}

View File

@@ -960,19 +960,18 @@ public class HotakeAiCommonToolsServiceImpl extends BaseServiceImpl implements I
String prompt = AiCommonPromptConstants.initializationAiInterviewQuestionsPrompt(); String prompt = AiCommonPromptConstants.initializationAiInterviewQuestionsPrompt();
String resultJson = "Please generate AI interview questions based on the following job description\n" + String userPrompt_1 = "Please generate AI interview questions based on the following job description\n" +
"\n" + "\n" +
"**Job Information**\n" + "**Job Information**\n" +
"职位名称:【\n" + "Job Title"+rolesInfo.getRoleName()+"\n" +
"技术要求:【\n" + "Technical Requirements"+rolesInfo.getRequiredSkillsJson()+"-"+rolesInfo.getNiceToHaveSkillsJson()+"\n" +
"经验要求:【\n" + "Experience Requirements"+rolesInfo.getJobExperience()+"\n" +
"面试时长:【】\n" + "Interview Duration:【】\n" +
"公司文化:【】\n" + "Company Culture:【】\n" +
"特殊要求:【\n" + "Special Requirements"+rolesInfo.getAboutRole()+"\n" +
"`;"; "`;";
log.info("招聘链接信息提取:{}",resultJson); log.info("AI面试问题生成:{}",userPrompt_1);
//处理岗位信息补充 //处理岗位信息补充
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(); List<Map<String, String>> listOne = new LinkedList();
Map<String, String> mapEntityOne = new HashMap<>(); Map<String, String> mapEntityOne = new HashMap<>();
mapEntityOne.put("role", "system"); mapEntityOne.put("role", "system");
@@ -985,8 +984,8 @@ public class HotakeAiCommonToolsServiceImpl extends BaseServiceImpl implements I
String promptJsonOne = JSONUtil.toJsonStr(listOne); String promptJsonOne = JSONUtil.toJsonStr(listOne);
String resultStrOne = chatGPTClient.handleAiChat(promptJsonOne,"RLINKAL"); String resultStrOne = chatGPTClient.handleAiChat(promptJsonOne,"RLINKAL");
String resultJsonOne = resultStrOne.replaceAll("```json","").replaceAll("```",""); String resultJsonOne = resultStrOne.replaceAll("```json","").replaceAll("```","");
log.info("招聘信息补全:{}",resultJsonOne); log.info("AI面试问题生成结果:{}",resultJsonOne);
return ""; return resultJsonOne;
} }
} }