Commit 5a1f974b authored by 姜耀祖's avatar 姜耀祖

funcation call测试

parent 6564ef5b
Pipeline #21232 passed with stages
in 5 minutes and 5 seconds
package com.infoepoch.pms.dispatchassistant.controller.langchain.test;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.annotation.PreDestroy;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.SocketTimeoutException;
import java.util.*;
@RestController
@RequestMapping("/test/chat")
public class ChatController {
@Autowired
private ObjectMapper objectMapper;
private static final CloseableHttpClient httpClient;
private static final String QW_API_KEY = "sk-7849b6c296b04cbc8df78521c4e0ecb2";
private static final Logger log = LoggerFactory.getLogger(ChatController.class);
static {
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
connManager.setMaxTotal(100);
connManager.setDefaultMaxPerRoute(20);
httpClient = HttpClients.custom()
.setConnectionManager(connManager)
.build();
}
@GetMapping(
value = "/chat-stream",
produces = "text/event-stream;charset=UTF-8"
)
public SseEmitter chatStream(@RequestParam("message") String message, HttpServletResponse rresponse) throws IOException {
SseEmitter emitter = new SseEmitter(30 * 60 * 1000L);
List<Message> messages = new ArrayList<>();
Message m = new Message();
m.setRole("user");
m.setContent(message);
messages.add(m);
rresponse.setStatus(HttpServletResponse.SC_OK);
rresponse.setContentType("text/event-stream;charset=UTF-8");
rresponse.setCharacterEncoding("UTF-8");
rresponse.flushBuffer();
new Thread(() -> {
try{
//tool工具构建
WeatherParameters weatherParameters = new WeatherParameters();
Map<String,Object> parametersMap = new HashMap<>();
Map<String,Object> parameters = new HashMap<>();
parameters.put("type", "string");
parameters.put("description", "专家的专业领域或关键词,如\"通信\"或\"运维\"");
Map<String,Object> parameters1 = new HashMap<>();
parameters1.put("type", "string");
parameters1.put("description", "可选 专家所在的城市,如\"南京\"或\"苏州\"");
parametersMap.put("query", parameters);
parametersMap.put("city", parameters1);
weatherParameters.setType("object");
weatherParameters.setProperties(parametersMap);
QWFuncation qwFuncation = new QWFuncation();
qwFuncation.setName("search_expert");
qwFuncation.setDescription("据专业领域和可选地市,查找符合条件的自有人员专家列表");
qwFuncation.setParameters(weatherParameters);
QWTool qwTool = new QWTool();
qwTool.setType("function");
qwTool.setFunction(qwFuncation);
List<QWTool> qwTools = new ArrayList<>();
qwTools.add(qwTool);
// 请求体构建优化
// ChatMessageDTO messageDTO = new ChatMessageDTO("qwq-plus",
// messages, true);
ChatMessageToolsDto messageDTO = new ChatMessageToolsDto("qwq-plus",
messages, true, qwTools);
// 使用HttpComponents更优雅的请求构建方式
String reuqestData = objectMapper.writeValueAsString(messageDTO);
HttpPost post = new HttpPost("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions");
post.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + QW_API_KEY);
post.setEntity(new StringEntity(reuqestData,
ContentType.APPLICATION_JSON));
log.info("发起对话请求:" + reuqestData);
// 执行请求(带重试)
try (CloseableHttpResponse response = httpClient.execute(post)) {
log.info("请求成功!");
processStreamResponse(response, emitter);
}
} catch (Exception e) {
handleException(e, emitter);
}
}).start();
// 超时和完成回调
emitter.onTimeout(() -> log.warn("SSE连接超时"));
emitter.onCompletion(() -> log.info("SSE连接完成"));
return emitter;
}
// 流式响应处理私有方法
private void processStreamResponse(HttpResponse response, SseEmitter emitter) throws IOException {
StringBuilder toolArgumentsBuffer = new StringBuilder();
boolean isToolCalling = false;
String toolName = null;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(response.getEntity().getContent()))) {
String line;
while ((line = reader.readLine()) != null) {
if (!line.startsWith("data: ")) continue;
String jsonStr = line.substring(6).trim();
if ("[DONE]".equals(jsonStr)) break;
JsonNode node = objectMapper.readTree(jsonStr);
JsonNode delta = node.at("/choices/0/delta");
// 处理工具调用
if (delta.has("tool_calls")) {
isToolCalling = true;
JsonNode toolCall = delta.get("tool_calls").get(0);
if (toolName == null && toolCall.has("function") && toolCall.get("function").has("name")) {
toolName = toolCall.get("function").get("name").asText();
}
String part = toolCall.get("function").path("arguments").asText("");
toolArgumentsBuffer.append(part);
continue;
}
// 处理普通 content 输出
String content = delta.path("content").asText(null);
String reasoning = delta.path("reasoning_content").asText(null);
if (content == null || content.isEmpty()) {
continue;
}
log.info(content);
emitter.send(SseEmitter.event().data(content).id(UUID.randomUUID().toString()));
}
if (isToolCalling) {
String toolArgumentsJson = toolArgumentsBuffer.toString();
JsonNode args = objectMapper.readTree(toolArgumentsJson);
String city = args.path("city").asText();
String query = args.path("query").asText();
log.info("准备调用工具函数 {}: city={}, query={}", toolName, city, query);
String result = searchExpert(city, query);
emitter.send(SseEmitter.event().data(result).id(UUID.randomUUID().toString()));
}
emitter.complete();
}
}
private String searchExpert(String city, String query) {
return "为您推荐如下专家:姓名:张三,城市:" + city + ", 领域:" + query +",荣誉:。。。";
}
@PreDestroy
public void destroy() throws IOException {
httpClient.close(); // 程序退出时关闭
}
// 统一异常处理
private void handleException(Exception e, SseEmitter emitter) {
log.error("API调用异常", e);
if (e instanceof SocketTimeoutException) {
emitter.completeWithError(new RuntimeException("连接DeepSeek服务超时"));
} else {
emitter.completeWithError(new RuntimeException("服务内部错误"));
}
}
}
\ No newline at end of file
package com.infoepoch.pms.dispatchassistant.controller.langchain.test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
public class ChatMessageDTO {
private String model;
private List<Message> messages;
private boolean stream;
public ChatMessageDTO(){}
}
package com.infoepoch.pms.dispatchassistant.controller.langchain.test;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author jiangyz
* @date 2025/5/9 14:32
*/
@Getter
@Setter
@AllArgsConstructor
public class ChatMessageToolsDto {
private String model;
private List<Message> messages;
private boolean stream;
private List<QWTool> tools;
public ChatMessageToolsDto(){}
}
package com.infoepoch.pms.dispatchassistant.controller.langchain.test;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
public class Message {
private String role;
private String content;
public Message(){}
}
package com.infoepoch.pms.dispatchassistant.controller.langchain.test;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.poi.ss.formula.functions.T;
/**
* @author jiangyz
* @date 2025/5/9 13:49
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class QWFuncation {
private String name;
private String description;
private Object parameters;
}
package com.infoepoch.pms.dispatchassistant.controller.langchain.test;
import lombok.*;
import org.apache.poi.ss.formula.functions.T;
/**
* @author jiangyz
* @date 2025/5/9 13:49
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class QWTool {
private String type;
private QWFuncation function;
}
package com.infoepoch.pms.dispatchassistant.controller.langchain.test;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
import java.util.Map;
/**
* @author jiangyz
* @date 2025/5/9 13:53
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class WeatherParameters {
private String type;
private Map<String,Object> properties;
}
package com.infoepoch.pms.dispatchassistant.controller.langchain.test;
/**
* @author jiangyz
* @date 2025/5/9 13:48
*/
public class WeatherTool {
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<input type="text" id="input" placeholder="输入问题...">
<button onclick="startChat()">发送</button>
<div id="output" style="white-space: pre-wrap;"></div>
</div>
<script>
function startChat() {
const input = document.getElementById('input').value;
const eventSource = new EventSource(`/dispatch-assistant/test/chat/chat-stream?message=${encodeURIComponent(input)}`);
eventSource.onmessage = (e) => {
document.getElementById('output').textContent += e.data;
window.scrollTo(0, document.body.scrollHeight);
};
eventSource.onerror = () => {
eventSource.close();
//alert('连接异常,请重试!');
};
}
</script>
</body>
</html>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment