mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-04-08 06:27:36 +00:00
add zhipu
This commit is contained in:
@@ -59,9 +59,10 @@ dependencies {
|
|||||||
implementation("org.flywaydb:flyway-database-postgresql:$flywayVersion")
|
implementation("org.flywaydb:flyway-database-postgresql:$flywayVersion")
|
||||||
implementation("com.github.ben-manes.caffeine:caffeine:3.2.0")
|
implementation("com.github.ben-manes.caffeine:caffeine:3.2.0")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-quartz")
|
implementation("org.springframework.boot:spring-boot-starter-quartz")
|
||||||
implementation("dev.langchain4j:langchain4j-open-ai:1.0.0")
|
|
||||||
implementation("io.projectreactor:reactor-core:3.7.6")
|
|
||||||
implementation("dev.langchain4j:langchain4j:1.0.0")
|
implementation("dev.langchain4j:langchain4j:1.0.0")
|
||||||
|
implementation("dev.langchain4j:langchain4j-open-ai:1.0.0")
|
||||||
|
implementation("dev.langchain4j:langchain4j-community-zhipu-ai:1.0.1-beta6")
|
||||||
|
implementation("io.projectreactor:reactor-core:3.7.6")
|
||||||
testImplementation("org.testcontainers:junit-jupiter:$testcontainersVersion")
|
testImplementation("org.testcontainers:junit-jupiter:$testcontainersVersion")
|
||||||
testImplementation("org.testcontainers:postgresql:$testcontainersVersion")
|
testImplementation("org.testcontainers:postgresql:$testcontainersVersion")
|
||||||
testImplementation("org.testcontainers:testcontainers-bom:$testcontainersVersion")
|
testImplementation("org.testcontainers:testcontainers-bom:$testcontainersVersion")
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ import dev.langchain4j.service.TokenStream;
|
|||||||
import dev.langchain4j.service.UserMessage;
|
import dev.langchain4j.service.UserMessage;
|
||||||
import dev.langchain4j.service.memory.ChatMemoryAccess;
|
import dev.langchain4j.service.memory.ChatMemoryAccess;
|
||||||
|
|
||||||
public interface DeepSeekChatAssistant extends ChatMemoryAccess {
|
public interface AiChatAssistant extends ChatMemoryAccess {
|
||||||
TokenStream chat(@MemoryId String memoryId, @UserMessage String userMessage);
|
TokenStream chat(@MemoryId String memoryId, @UserMessage String userMessage);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.zl.mjga.config.ai;
|
package com.zl.mjga.config.ai;
|
||||||
|
|
||||||
|
import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel;
|
||||||
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
|
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
|
||||||
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
|
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
|
||||||
import dev.langchain4j.service.AiServices;
|
import dev.langchain4j.service.AiServices;
|
||||||
@@ -12,6 +13,18 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
public class ChatModelConfig {
|
public class ChatModelConfig {
|
||||||
|
|
||||||
private final DeepSeekConfiguration deepSeekConfiguration;
|
private final DeepSeekConfiguration deepSeekConfiguration;
|
||||||
|
private final ZhiPuConfiguration zhiPuConfiguration;
|
||||||
|
private final PromptConfiguration promptConfiguration;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ZhipuAiStreamingChatModel zhipuChatModel() {
|
||||||
|
return ZhipuAiStreamingChatModel.builder()
|
||||||
|
.model(zhiPuConfiguration.getModelName())
|
||||||
|
.apiKey(zhiPuConfiguration.getApiKey())
|
||||||
|
.logRequests(true)
|
||||||
|
.logResponses(true)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OpenAiStreamingChatModel deepSeekChatModel() {
|
public OpenAiStreamingChatModel deepSeekChatModel() {
|
||||||
@@ -23,10 +36,19 @@ public class ChatModelConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public DeepSeekChatAssistant deepSeekChatAssistant(OpenAiStreamingChatModel deepSeekChatModel) {
|
public AiChatAssistant deepSeekChatAssistant(OpenAiStreamingChatModel deepSeekChatModel) {
|
||||||
return AiServices.builder(DeepSeekChatAssistant.class)
|
return AiServices.builder(AiChatAssistant.class)
|
||||||
.streamingChatModel(deepSeekChatModel)
|
.streamingChatModel(deepSeekChatModel)
|
||||||
.systemMessageProvider(chatMemoryId -> deepSeekConfiguration.getPrompt().getSystem())
|
.systemMessageProvider(chatMemoryId -> promptConfiguration.getSystem())
|
||||||
|
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AiChatAssistant zhiPuChatAssistant(ZhipuAiStreamingChatModel zhipuChatModel) {
|
||||||
|
return AiServices.builder(AiChatAssistant.class)
|
||||||
|
.streamingChatModel(zhipuChatModel)
|
||||||
|
.systemMessageProvider(chatMemoryId -> promptConfiguration.getSystem())
|
||||||
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
|
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
package com.zl.mjga.config.ai;
|
package com.zl.mjga.config.ai;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.core.io.ResourceLoader;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@@ -15,22 +9,7 @@ import org.springframework.stereotype.Component;
|
|||||||
@ConfigurationProperties(prefix = "deep-seek")
|
@ConfigurationProperties(prefix = "deep-seek")
|
||||||
public class DeepSeekConfiguration {
|
public class DeepSeekConfiguration {
|
||||||
|
|
||||||
@jakarta.annotation.Resource private ResourceLoader resourceLoader;
|
|
||||||
|
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
private String apiKey;
|
private String apiKey;
|
||||||
private Prompt prompt;
|
|
||||||
private String modelName;
|
private String modelName;
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class Prompt {
|
|
||||||
private String system;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void init() throws IOException {
|
|
||||||
Resource resource = resourceLoader.getResource("classpath:prompt.txt");
|
|
||||||
prompt = new Prompt();
|
|
||||||
prompt.setSystem(Files.readString(Paths.get(resource.getURI())));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.zl.mjga.config.ai;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
public class PromptConfiguration {
|
||||||
|
|
||||||
|
@jakarta.annotation.Resource private ResourceLoader resourceLoader;
|
||||||
|
private String system;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() throws IOException {
|
||||||
|
Resource resource = resourceLoader.getResource("classpath:prompt.txt");
|
||||||
|
system = Files.readString(Paths.get(resource.getURI()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.zl.mjga.config.ai;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "zhipu")
|
||||||
|
public class ZhiPuConfiguration {
|
||||||
|
|
||||||
|
private String baseUrl;
|
||||||
|
private String apiKey;
|
||||||
|
private String modelName;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.zl.mjga.controller;
|
package com.zl.mjga.controller;
|
||||||
|
|
||||||
import com.zl.mjga.service.DeepSeekAiService;
|
import com.zl.mjga.service.AiChatService;
|
||||||
import dev.langchain4j.service.TokenStream;
|
import dev.langchain4j.service.TokenStream;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
@@ -17,12 +17,12 @@ import reactor.core.publisher.Sinks;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class AiController {
|
public class AiController {
|
||||||
|
|
||||||
private final DeepSeekAiService deepSeekAiService;
|
private final AiChatService aiChatService;
|
||||||
|
|
||||||
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
public Flux<String> chat(Principal principal, @RequestBody String userMessage) {
|
public Flux<String> chat(Principal principal, @RequestBody String userMessage) {
|
||||||
Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();
|
Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();
|
||||||
TokenStream chat = deepSeekAiService.chat(principal.getName(), userMessage);
|
TokenStream chat = aiChatService.chatWithZhiPu(principal.getName(), userMessage);
|
||||||
chat.onPartialResponse(text -> sink.tryEmitNext(text.replace(" ", "␣").replace("\t", "⇥")))
|
chat.onPartialResponse(text -> sink.tryEmitNext(text.replace(" ", "␣").replace("\t", "⇥")))
|
||||||
.onCompleteResponse(
|
.onCompleteResponse(
|
||||||
r -> {
|
r -> {
|
||||||
|
|||||||
24
backend/src/main/java/com/zl/mjga/service/AiChatService.java
Normal file
24
backend/src/main/java/com/zl/mjga/service/AiChatService.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package com.zl.mjga.service;
|
||||||
|
|
||||||
|
import com.zl.mjga.config.ai.AiChatAssistant;
|
||||||
|
import dev.langchain4j.service.TokenStream;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class AiChatService {
|
||||||
|
|
||||||
|
private final AiChatAssistant deepSeekChatAssistant;
|
||||||
|
private final AiChatAssistant zhiPuChatAssistant;
|
||||||
|
|
||||||
|
public TokenStream chatWithDeepSeek(String sessionIdentifier, String userMessage) {
|
||||||
|
return deepSeekChatAssistant.chat(sessionIdentifier, userMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenStream chatWithZhiPu(String sessionIdentifier, String userMessage) {
|
||||||
|
return zhiPuChatAssistant.chat(sessionIdentifier, userMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.zl.mjga.service;
|
|
||||||
|
|
||||||
import com.zl.mjga.config.ai.DeepSeekChatAssistant;
|
|
||||||
import dev.langchain4j.service.TokenStream;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
|
||||||
public class DeepSeekAiService {
|
|
||||||
|
|
||||||
private final DeepSeekChatAssistant deepSeekChatAssistant;
|
|
||||||
|
|
||||||
public TokenStream chat(String sessionIdentifier, String userMessage) {
|
|
||||||
return deepSeekChatAssistant.chat(sessionIdentifier, userMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
deep-seek:
|
deep-seek:
|
||||||
base-url: "https://api.deepseek.com"
|
base-url: "https://api.deepseek.com"
|
||||||
api-key: "sk-3633b0cd40884b27aa8402a1c5dc029d"
|
api-key: "sk-3633b0cd40884b27aa8402a1c5dc029d"
|
||||||
model-name: "deepseek-chat"
|
model-name: "deepseek-chat"
|
||||||
|
zhipu:
|
||||||
|
base-url: "https://open.bigmodel.cn/"
|
||||||
|
api-key: ""
|
||||||
|
model-name: "glm-4-flash"
|
||||||
@@ -80,16 +80,15 @@ marked.setOptions({
|
|||||||
const renderMarkdown = (content: string) => {
|
const renderMarkdown = (content: string) => {
|
||||||
if (!content) return '';
|
if (!content) return '';
|
||||||
|
|
||||||
// 替换所有空白占位符(包括前后端约定的特殊字符)
|
|
||||||
const restoredContent = content
|
const restoredContent = content
|
||||||
.replace(/␣/g, ' ') // 普通空格
|
.replace(/␣/g, ' ')
|
||||||
.replace(/⇥/g, '\t') // 制表符
|
.replace(/⇥/g, '\t')
|
||||||
.replace(//g, '\n'); // 如果后端也处理了换行符
|
.replace(//g, '\n');
|
||||||
|
|
||||||
|
|
||||||
// 处理Markdown中的代码块缩进
|
|
||||||
const processedContent = restoredContent
|
const processedContent = restoredContent
|
||||||
.replace(/^(\s*)(`{3,})/gm, '$1$2') // 保留代码块前的空格
|
.replace(/^(\s*)(`{3,})/gm, '$1$2')
|
||||||
.replace(/(\s+)`/g, '$1`'); // 保留代码内联前的空格
|
.replace(/(\s+)`/g, '$1`');
|
||||||
|
|
||||||
const rawHtml = marked(processedContent);
|
const rawHtml = marked(processedContent);
|
||||||
return DOMPurify.sanitize(rawHtml as string);
|
return DOMPurify.sanitize(rawHtml as string);
|
||||||
@@ -104,10 +103,10 @@ const chatElements = computed(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(messages, (newVal) => {
|
// watch(messages, (newVal) => {
|
||||||
console.log('原始消息:', newVal[newVal.length - 1]);
|
// console.log('原始消息:', newVal[newVal.length - 1]);
|
||||||
console.log('处理后HTML:', renderMarkdown(newVal[newVal.length - 1]));
|
// console.log('处理后HTML:', renderMarkdown(newVal[newVal.length - 1]));
|
||||||
}, { deep: true });
|
// }, { deep: true });
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
chatElements,
|
chatElements,
|
||||||
|
|||||||
Reference in New Issue
Block a user