This commit is contained in:
aruo
2026-05-31 21:45:14 +08:00
parent d4b5015beb
commit ac08345369
67 changed files with 11120 additions and 387 deletions
@@ -0,0 +1,92 @@
package org.example.service;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import static org.junit.jupiter.api.Assertions.*;
/**
* Chat + Embedding 解耦验证测试
* <p>
* 验证 ChatModel Bean 注入、ModelRoutingConfig 路由、ChatService 接口兼容。
* Chat: DeepSeek via OpenAI-compatible API
* Embedding: Ollama BGE-M3 (需要本地 ollama 运行)
*/
@SpringBootTest
@DisplayName("Chat + Embedding 解耦验证")
class ChatAndEmbeddingSmokeTest {
@Autowired
private ApplicationContext context;
@Autowired
private ChatModel chatModel;
@Autowired
private ChatService chatService;
/**
* 提供 mock ToolCallbackProvider(MCP 已禁用时需要)
*/
@TestConfiguration
static class MockToolConfig {
@Bean
public ToolCallbackProvider toolCallbackProvider() {
return () -> new ToolCallback[0];
}
}
@Test
@DisplayName("Spring 容器启动成功")
void contextLoads() {
assertNotNull(context, "Spring 容器应为非空");
assertNotNull(chatModel, "ChatModel Bean 应注入成功");
assertNotNull(chatService, "ChatService Bean 应注入成功");
}
@Test
@DisplayName("ModelRoutingConfig @Primary ChatModel 生效")
void chatModelPrimaryBeanWorks() {
assertNotNull(chatModel, "@Primary ChatModel 应被自动注入");
System.out.println("✓ ChatModel 类型: " + chatModel.getClass().getName());
// 验证路由到 openAiChatModel (DeepSeek)
assertTrue(context.containsBean("openAiChatModel"), "openAiChatModel 应存在");
System.out.println(" Chat → openAiChatModel (DeepSeek) ✓");
}
@Test
@DisplayName("ChatService.createReactAgent 接受 ChatModel 接口")
void chatServiceAcceptsChatModelInterface() {
var agent = chatService.createReactAgent(chatModel, "测试系统提示词");
assertNotNull(agent, "ReactAgent 应创建成功");
assertEquals("intelligent_assistant", agent.name());
System.out.println("✓ ReactAgent 创建成功: " + agent.name());
}
@Test
@DisplayName("ChatModel 实现 ChatModel 接口(类型安全验证)")
void chatModelIsProperType() {
assertNotNull(chatModel, "注入的 Bean 应为 ChatModel 实例");
System.out.println("✓ ChatModel 接口实现: " + chatModel.getClass().getSimpleName());
}
@Test
@DisplayName("EmbeddingModel 状态")
void embeddingModelStatus() {
boolean hasEmbedding = context.containsBean("embeddingModel");
if (hasEmbedding) {
System.out.println("✓ EmbeddingModel 已配置");
} else {
System.out.println("⚠ EmbeddingModel 未找到 — 检查 Ollama 是否运行");
}
}
}
@@ -0,0 +1,161 @@
package org.example.service;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* 全链路验证:DeepSeek → BGE-M3 → Milvus
*/
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DisplayName("DeepSeek → BGE-M3 → Milvus 全链路")
class FullPipelineSmokeTest {
@Autowired
private ChatModel chatModel;
@Autowired
private EmbeddingModel embeddingModel;
@Autowired
private VectorEmbeddingService vectorEmbeddingService;
@Autowired
private VectorSearchService vectorSearchService;
@TestConfiguration
static class MockToolConfig {
@Bean
public ToolCallbackProvider toolCallbackProvider() {
return () -> new ToolCallback[0];
}
}
// ===== ① Chat: DeepSeek =====
@Test
@Order(1)
@DisplayName("Chat: DeepSeek 聊天验证")
void chatDeepSeekWorks() {
System.out.println("\n===== ① Chat: DeepSeek =====");
System.out.println("ChatModel: " + chatModel.getClass().getSimpleName());
System.out.println("ChatOptions: " + chatModel.toString());
// 直接调用 chat
var response = chatModel.call(new Prompt("请用一句话介绍你自己"));
String text = response.getResult().getOutput().getText();
assertNotNull(text);
assertFalse(text.isEmpty());
System.out.println("Response: " + text.substring(0, Math.min(200, text.length())) + "...");
System.out.println("Chat ✓");
}
// ===== ② Embedding: BGE-M3 via SiliconFlow =====
@Test
@Order(2)
@DisplayName("Embedding: BGE-M3 向量生成验证")
void embeddingBgeM3Works() {
System.out.println("\n===== ② Embedding: BGE-M3 (SiliconFlow) =====");
System.out.println("EmbeddingModel: " + embeddingModel.getClass().getSimpleName());
String text = "你好,这是一条测试文本";
List<Float> vector = vectorEmbeddingService.generateEmbedding(text);
assertNotNull(vector);
assertFalse(vector.isEmpty());
assertEquals(1024, vector.size(), "BGE-M3 应返回 1024 维向量");
// 非零校验
boolean hasNonZero = vector.stream().anyMatch(v -> Math.abs(v) > 1e-6);
assertTrue(hasNonZero, "向量不能全为零");
System.out.println("维度: " + vector.size());
System.out.println("前5维: " + vector.subList(0, Math.min(5, vector.size())));
System.out.println("Embedding ✓");
}
@Test
@Order(3)
@DisplayName("Embedding: BGE-M3 批量向量生成验证")
void embeddingBatchWorks() {
System.out.println("\n===== ③ Embedding 批量 =====");
List<String> texts = List.of("文本一", "文本二", "文本三");
List<List<Float>> results = vectorEmbeddingService.generateEmbeddings(texts);
assertEquals(3, results.size());
for (List<Float> r : results) {
assertEquals(1024, r.size());
}
System.out.println("批量生成: " + results.size() + " 个 向量,各 " + results.get(0).size() + " 维 ✓");
}
// ===== ③ Milvus: 向量搜索 =====
@Test
@Order(4)
@DisplayName("Milvus: 连接 + 搜索验证")
void milvusSearchWorks() {
System.out.println("\n===== ④ Milvus: 向量搜索 =====");
// 用 BGE-M3 生成查询向量
String query = "内部文档";
List<Float> queryVector = vectorEmbeddingService.generateQueryVector(query);
assertNotNull(queryVector);
assertEquals(1024, queryVector.size());
// 搜索
List<VectorSearchService.SearchResult> results =
vectorSearchService.searchSimilarDocuments(query, 3);
assertNotNull(results);
System.out.println("查询: " + query);
System.out.println("返回: " + results.size() + " 条");
if (!results.isEmpty()) {
// 至少有结果,验证结构
for (int i = 0; i < results.size(); i++) {
var r = results.get(i);
assertNotNull(r.getId());
assertNotNull(r.getContent());
System.out.println(" [" + (i + 1) + "] id=" + r.getId()
+ ", score=" + String.format("%.4f", r.getScore())
+ ", content=" + r.getContent().substring(0, Math.min(50, r.getContent().length())) + "...");
}
} else {
System.out.println("(Milvus 中暂无数据,但连接正常)");
}
System.out.println("Milvus ✓");
}
// ===== 汇总 =====
@Test
@Order(5)
@DisplayName("总结")
void summary() {
System.out.println("\n==========================================");
System.out.println("全链路验证完成:");
System.out.println(" ① Chat → DeepSeek ✓");
System.out.println(" ② Embedding → BGE-M3 ✓ (SiliconFlow, 1024维)");
System.out.println(" ③ 向量存储 → Milvus ✓ (Zilliz Cloud)");
System.out.println("==========================================");
}
}