当前位置: 首页 > news >正文

LangChain4j入门学习

LangChain4j 学习文档

目录

  1. 环境准备与依赖安装
  2. 简单流式调用
  3. 记忆对话功能
  4. 工具调用
  5. 工具流式返回调用结果
  6. Redis流式调用
  7. 总结

环境准备与依赖安装

1. 项目依赖配置

首先,我们需要在 pom.xml 中添加必要的依赖:

<!-- LangChain4j 核心依赖 -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId><version>1.4.0</version>
</dependency><!-- LangChain4j OpenAI Spring Boot Starter -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai-spring-boot-starter</artifactId><version>1.4.0-beta10</version>
</dependency><!-- LangChain4j Reactor 支持 -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId><version>1.4.0-beta10</version>
</dependency><!-- LangChain4j Redis 支持 -->
<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-redis-spring-boot-starter</artifactId><version>1.1.0-beta7</version>
</dependency><!-- Spring Boot Web Starter -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!-- Lombok -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>

2. 配置文件设置

application.yml 中配置AI模型参数:

# AI 配置
langchain4j:open-ai:chat-model:base-url: http://your-ai-service-url/v1  # AI服务地址api-key: your-api-key                    # API密钥model-name: Qwen3-8B-FP8                # 模型名称log-requests: true                       # 是否记录请求日志log-responses: true                      # 是否记录响应日志# 服务器配置
server:port: 9999servlet:context-path: /api# Redis 配置(用于记忆功能)
spring:data:redis:host: 127.0.0.1port: 6379database: 0password:  # 如果有密码则填写ttl: 3600  # 过期时间(秒)

3. 基础配置类

创建流式聊天模型配置:

@Configuration
public class DeepSeekStreamingChatModel {@Value("${langchain4j.open-ai.chat-model.api-key}")private String apiKey;@Value("${langchain4j.open-ai.chat-model.base-url}")private String baseUrl;@Value("${langchain4j.open-ai.chat-model.model-name}")private String modelName;@Value("${langchain4j.open-ai.chat-model.log-requests}")private Boolean logRequests;@Value("${langchain4j.open-ai.chat-model.log-responses}")private Boolean logResponses;@Bean("deepStreamingChatModel")public StreamingChatModel streamingChatModel() {return OpenAiStreamingChatModel.builder().baseUrl(baseUrl).apiKey(apiKey).modelName(modelName).temperature(0.7)           // 控制回答的随机性.maxTokens(1000)           // 最大token数.logRequests(logRequests)   // 记录请求日志.logResponses(logResponses) // 记录响应日志.returnThinking(true)       // 返回思考过程.build();}
}

配置说明:

  • baseUrl: AI服务的API地址
  • apiKey: 访问AI服务的密钥
  • modelName: 使用的模型名称
  • temperature: 控制回答的随机性(0-1,越高越随机)
  • maxTokens: 单次回答的最大token数
  • returnThinking: 是否返回AI的思考过程

简单流式调用

1. 基础概念

流式调用是指AI模型逐步返回回答内容,而不是等待完整回答后再一次性返回。这种方式可以提供更好的用户体验,让用户能够实时看到AI的思考过程。

2. 创建AI服务接口

首先,我们需要定义一个AI服务接口:

public interface AssistantChatResponse {@SystemMessage("你是一个java架构师,如果需要调用工具的话即时通知用户,如果不需要调用工具的话,不要给用户返回多余的关于工具的信息")TokenStream chat(@MemoryId String memoryId, @UserMessage String query);
}

接口说明:

  • @SystemMessage: 定义系统提示词,设置AI的角色和行为
  • @MemoryId: 用于标识对话会话,实现记忆功能
  • @UserMessage: 标记用户输入的消息
  • TokenStream: 返回流式响应

3. 创建响应实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class XingGuiChatResponse {private String content;           // AI回答内容private String reasoningContent;  // AI思考过程private String toolName;         // 工具名称private String toolOutput;       // 工具输出结果
}

4. 简单流式调用实现

@RestController
@Slf4j
public class TestController {@Resource(name = "deepStreamingChatModel")private StreamingChatModel streamingChatModel;@GetMapping(value = "/simple-stream", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})public Flux<XingGuiChatResponse> simpleStream(@RequestParam("query") String query) {// 创建AI服务AiServices<AssistantChatResponse> chatClient = AiServices.builder(AssistantChatResponse.class);AssistantChatResponse assistant = chatClient.streamingChatModel(streamingChatModel).build();return Flux.create(sink -> {assistant.chat("simple-session", query).onPartialResponse(partialResponse -> {// 处理部分响应XingGuiChatResponse response = XingGuiChatResponse.builder().content(partialResponse).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);}).onCompleteResponse(completeResponse -> {// 处理完整响应XingGuiChatResponse response = XingGuiChatResponse.builder().content(completeResponse.aiMessage().text()).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);sink.complete();}).onError(throwable -> {log.error("流式调用出错: ", throwable);sink.error(throwable);}).start();});}
}

5. 测试简单流式调用

使用curl测试:

curl -N "http://localhost:9999/api/simple-stream?query=你好,请介绍一下Java"

流式调用特点:

  • 实时返回:AI回答会逐步返回,不需要等待完整回答
  • 更好的用户体验:用户可以实时看到AI的思考过程
  • 支持中断:客户端可以随时中断请求
  • 资源友好:不需要等待完整响应,可以更早释放资源

记忆对话功能

1. 记忆功能概述

记忆对话功能允许AI记住之前的对话内容,实现上下文连贯的对话体验。LangChain4j提供了多种记忆存储方式,包括内存存储、Redis存储等。

2. Redis记忆存储配置

首先配置Redis作为记忆存储:

@Configuration
@ConfigurationProperties(prefix = "spring.data.redis")
@Data
public class RedisChatMemoryStoreConfig {private String host;private int port;private long ttl;private String password;@Bean("redisChatMemoryStore")public RedisChatMemoryStore redisChatMemoryStore() {return RedisChatMemoryStore.builder().host(host).port(port).password(password).ttl(ttl)  // 记忆过期时间.build();}
}

3. 带记忆的AI服务配置

@Configuration
public class DeepSeekGeneratorCodeService {@Resource(name = "redisChatMemoryStore")private RedisChatMemoryStore redisChatMemoryStore;@Resource(name = "deepStreamingChatModel")private StreamingChatModel streamingChatModel;@Beanpublic AssistantChatResponse assistantChatResponse() {return AiServices.builder(AssistantChatResponse.class).streamingChatModel(streamingChatModel).chatMemoryProvider(memory -> {return MessageWindowChatMemory.builder().id(memory)                    // 记忆ID.chatMemoryStore(redisChatMemoryStore)  // 使用Redis存储.maxMessages(20)              // 最大记忆消息数.build();}).build();}
}

4. 记忆对话实现

@GetMapping(value = "/chat-with-memory", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
public Flux<XingGuiChatResponse> chatWithMemory(@RequestParam("query") String query,@RequestParam("memoryId") String memoryId) {AiServices<AssistantChatResponse> chatClient = AiServices.builder(AssistantChatResponse.class);AssistantChatResponse assistant = chatClient.streamingChatModel(streamingChatModel).chatMemoryProvider(memory -> {return MessageWindowChatMemory.builder().id(memory).chatMemoryStore(redisChatMemoryStore).maxMessages(10)  // 保持最近10条对话.build();}).build();return Flux.create(sink -> {assistant.chat(memoryId, query).onPartialResponse(partialResponse -> {XingGuiChatResponse response = XingGuiChatResponse.builder().content(partialResponse).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);}).onCompleteResponse(completeResponse -> {XingGuiChatResponse response = XingGuiChatResponse.builder().content(completeResponse.aiMessage().text()).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);sink.complete();}).onError(throwable -> {log.error("记忆对话出错: ", throwable);sink.error(throwable);}).start();});
}

5. 记忆功能测试

测试记忆功能:

# 第一次对话
curl -N "http://localhost:9999/api/chat-with-memory?query=我叫张三&memoryId=user123"# 第二次对话(AI应该记住你的名字)
curl -N "http://localhost:9999/api/chat-with-memory?query=我的名字是什么?&memoryId=user123"

6. 记忆管理

// 清除特定用户的记忆
@GetMapping("/clear-memory")
public String clearMemory(@RequestParam("memoryId") String memoryId) {redisChatMemoryStore.deleteMessages(memoryId);return "记忆已清除";
}// 获取记忆统计信息
@GetMapping("/memory-stats")
public Map<String, Object> getMemoryStats(@RequestParam("memoryId") String memoryId) {List<ChatMessage> messages = redisChatMemoryStore.getMessages(memoryId);Map<String, Object> stats = new HashMap<>();stats.put("messageCount", messages.size());stats.put("memoryId", memoryId);return stats;
}

记忆功能特点:

  • 持久化存储:使用Redis存储对话历史
  • 自动过期:可设置记忆过期时间
  • 消息限制:可控制最大记忆消息数量
  • 会话隔离:不同memoryId的对话相互独立
  • 灵活管理:支持清除和查询记忆

工具调用

1. 工具调用概述

工具调用允许AI模型调用外部函数来获取信息或执行操作。这是实现AI Agent功能的核心特性,让AI能够与外部系统交互。

2. 创建自定义工具

首先创建一个简单的工具类:

@Slf4j
public class DateTimeTool {@Tool("获取当前时间,返回格式为 yyyy-MM-dd HH:mm:ss")public String getCurrentDateTime() {DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String time = LocalDateTime.now().format(formatter);log.info("调用获取时间工具,返回: {}", time);return time;}
}

工具注解说明:

  • @Tool: 标记方法为可调用的工具
  • 注解中的字符串:工具的描述,AI会根据这个描述决定是否调用该工具

3. 创建更复杂的工具

@Slf4j
public class CalculatorTool {@Tool("计算两个数字的和")public double add(@P("第一个数字") double a, @P("第二个数字") double b) {double result = a + b;log.info("计算: {} + {} = {}", a, b, result);return result;}@Tool("计算两个数字的差")public double subtract(@P("被减数") double a, @P("减数") double b) {double result = a - b;log.info("计算: {} - {} = {}", a, b, result);return result;}@Tool("计算两个数字的乘积")public double multiply(@P("第一个数字") double a, @P("第二个数字") double b) {double result = a * b;log.info("计算: {} * {} = {}", a, b, result);return result;}@Tool("计算两个数字的商")public double divide(@P("被除数") double a, @P("除数") double b) {if (b == 0) {throw new IllegalArgumentException("除数不能为零");}double result = a / b;log.info("计算: {} / {} = {}", a, b, result);return result;}
}

参数注解说明:

  • @P: 为工具参数提供描述,帮助AI理解参数含义

4. 配置带工具的AI服务

@GetMapping(value = "/chat-with-tools", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
public Flux<XingGuiChatResponse> chatWithTools(@RequestParam("query") String query,@RequestParam("memoryId") String memoryId) {AiServices<AssistantChatResponse> chatClient = AiServices.builder(AssistantChatResponse.class);AssistantChatResponse assistant = chatClient.streamingChatModel(streamingChatModel).tools(new DateTimeTool(), new CalculatorTool())  // 注册工具.chatMemoryProvider(memory -> {return MessageWindowChatMemory.builder().id(memory).chatMemoryStore(redisChatMemoryStore).maxMessages(10).build();}).build();return Flux.create(sink -> {assistant.chat(memoryId, query).onPartialResponse(partialResponse -> {XingGuiChatResponse response = XingGuiChatResponse.builder().content(partialResponse).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);}).onCompleteResponse(completeResponse -> {XingGuiChatResponse response = XingGuiChatResponse.builder().content(completeResponse.aiMessage().text()).reasoningContent(null).toolName(null).toolOutput(null).build();sink.next(response);sink.complete();}).onError(throwable -> {log.error("工具调用出错: ", throwable);sink.error(throwable);}).start();});
}

5. 工具调用测试

测试工具调用功能:

# 测试时间工具
curl -N "http://localhost:9999/api/chat-with-tools?query=现在几点了?&memoryId=user123"# 测试计算工具
curl -N "http://localhost:9999/api/chat-with-tools?query=帮我计算 25 + 17 等于多少?&memoryId=user123"# 测试复杂计算
curl -N "http://localhost:9999/api/chat-with-tools?query=计算 (10 + 5) * 3 - 8 的结果&memoryId=user123"

工具调用特点:

  • 自动选择:AI会根据用户问题自动选择合适的工具
  • 参数解析:AI能够理解并正确传递工具参数
  • 错误处理:工具执行失败时AI会给出相应提示
  • 结果整合:AI会将工具结果整合到回答中
  • 链式调用:AI可以连续调用多个工具完成复杂任务

工具流式返回调用结果

1. 工具流式调用概述

工具流式返回调用结果是指在工具调用过程中,实时返回工具的执行状态和结果,让用户能够看到AI的思考过程、工具调用过程以及最终结果。

2. 完整的工具流式调用实现

基于你现有的代码,这里展示完整的工具流式调用实现:

@GetMapping(value = "/ai", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
public Flux<XingGuiChatResponse> ai(@RequestParam("query") String query,@RequestParam("memoryId") String memoryId) {AiServices<AssistantChatResponse> chatClient = AiServices.builder(AssistantChatResponse.class);AssistantChatResponse xingGuiChatResponse = chatClient.streamingChatModel(streamingChatModel).tools(new DateTimeTool())  // 注册工具.chatMemoryProvider(memory -> {return MessageWindowChatMemory.builder().id(memory).chatMemoryStore(redisChatMemoryStore).maxMessages(10).build();}).build();return Flux.create(sink -> {xingGuiChatResponse.chat(memoryId, query)// 1. 处理AI思考过程.onPartialThinking(partialThinking -> {log.info("partialThinking: {}", partialThinking);XingGuiChatResponse chatResponse = XingGuiChatResponse.builder().reasoningContent(partialThinking.text())  // AI思考内容.content(null).toolName(null).toolOutput(null).build();sink.next(chatResponse);})// 2. 处理AI部分响应.onPartialResponse(partialResponse -> {XingGuiChatResponse chatResponse = XingGuiChatResponse.builder().reasoningContent(null).content(partialResponse)  // AI回答内容.toolName(null).toolOutput(null).build();sink.next(chatResponse);})// 3. 工具调用开始.beforeToolExecution(toolExecutionRequest -> {log.info("开始调用工具: {}", toolExecutionRequest.request().name());XingGuiChatResponse chatResponse = XingGuiChatResponse.builder().reasoningContent(null).content(null).toolName(toolExecutionRequest.request().name())  // 工具名称.toolOutput(toolExecutionRequest.request().arguments())  // 工具参数.build();sink.next(chatResponse);})// 4. 工具调用完成.onToolExecuted(toolExecutionResult -> {log.info("工具调用结束: {}", toolExecutionResult.request().name());XingGuiChatResponse chatResponse = XingGuiChatResponse.builder().reasoningContent(null).content(null).toolName(toolExecutionResult.request().name())  // 工具名称.toolOutput(toolExecutionResult.request().arguments())  // 工具参数.build();sink.next(chatResponse);})// 5. 完整响应完成.onCompleteResponse(completeResponse -> {log.info("completeResponse: {}", completeResponse);XingGuiChatResponse chatResponse = XingGuiChatResponse.builder().reasoningContent(null).content(completeResponse.aiMessage().text())  // 最终回答.toolName(null).toolOutput(null).build();sink.next(chatResponse);sink.complete();})// 6. 错误处理.onError(throwable -> {log.error("工具流式调用出错: ", throwable);sink.error(throwable);}).start();});
}

3. 流式响应数据结构

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class XingGuiChatResponse {private String content;           // AI回答内容private String reasoningContent;  // AI思考过程private String toolName;         // 工具名称private String toolOutput;       // 工具输出结果
}

4. 前端处理流式响应

前端Vue3代码示例:

<template><div class="chat-container"><div class="messages"><div v-for="(message, index) in messages" :key="index" class="message":class="{ 'ai-message': message.type === 'ai' }"><!-- AI思考过程 --><div v-if="message.reasoningContent" class="reasoning"><strong>AI思考:</strong> {{ message.reasoningContent }}</div><!-- 工具调用信息 --><div v-if="message.toolName" class="tool-call"><strong>调用工具:</strong> {{ message.toolName }}<div v-if="message.toolOutput" class="tool-args">参数: {{ message.toolOutput }}</div></div><!-- AI回答内容 --><div v-if="message.content" class="content">{{ message.content }}</div></div></div><div class="input-area"><input v-model="userInput" @keyup.enter="sendMessage"placeholder="请输入您的问题...":disabled="isLoading"/><button @click="sendMessage" :disabled="isLoading">{{ isLoading ? '发送中...' : '发送' }}</button></div></div>
</template><script setup>
import { ref, reactive } from 'vue'const userInput = ref('')
const isLoading = ref(false)
const messages = ref([])const sendMessage = async () => {if (!userInput.value.trim() || isLoading.value) returnconst query = userInput.valueuserInput.value = ''isLoading.value = true// 添加用户消息messages.value.push({type: 'user',content: query})try {const response = await fetch(`/api/ai?query=${encodeURIComponent(query)}&memoryId=user123`)const reader = response.body.getReader()const decoder = new TextDecoder()let currentMessage = {type: 'ai',content: '',reasoningContent: '',toolName: '',toolOutput: ''}while (true) {const { done, value } = await reader.read()if (done) breakconst chunk = decoder.decode(value)const lines = chunk.split('\n')for (const line of lines) {if (line.startsWith('data: ')) {try {const data = JSON.parse(line.slice(6))// 更新消息内容if (data.reasoningContent) {currentMessage.reasoningContent = data.reasoningContent}if (data.content) {currentMessage.content += data.content}if (data.toolName) {currentMessage.toolName = data.toolNamecurrentMessage.toolOutput = data.toolOutput}// 更新或添加消息到列表const existingIndex = messages.value.findIndex(m => m.type === 'ai' && m === currentMessage)if (existingIndex >= 0) {messages.value[existingIndex] = { ...currentMessage }} else {messages.value.push({ ...currentMessage })}} catch (e) {console.error('解析流式数据失败:', e)}}}}} catch (error) {console.error('发送消息失败:', error)messages.value.push({type: 'ai',content: '抱歉,发生了错误,请稍后重试。'})} finally {isLoading.value = false}
}
</script><style scoped>
.chat-container {max-width: 800px;margin: 0 auto;padding: 20px;
}.messages {height: 400px;overflow-y: auto;border: 1px solid #ddd;padding: 10px;margin-bottom: 20px;
}.message {margin-bottom: 15px;padding: 10px;border-radius: 5px;
}.ai-message {background-color: #f5f5f5;
}.reasoning {color: #666;font-style: italic;margin-bottom: 5px;
}.tool-call {color: #007bff;margin-bottom: 5px;
}.tool-args {font-size: 0.9em;color: #666;margin-left: 10px;
}.content {margin-top: 5px;
}.input-area {display: flex;gap: 10px;
}.input-area input {flex: 1;padding: 10px;border: 1px solid #ddd;border-radius: 5px;
}.input-area button {padding: 10px 20px;background-color: #007bff;color: white;border: none;border-radius: 5px;cursor: pointer;
}.input-area button:disabled {background-color: #ccc;cursor: not-allowed;
}
</style>

5. 测试工具流式调用

# 测试带工具调用的流式响应
curl -N "http://localhost:9999/api/ai?query=现在几点了?&memoryId=user123"# 测试复杂工具调用
curl -N "http://localhost:9999/api/ai?query=帮我计算 100 + 200 等于多少,然后告诉我现在的时间&memoryId=user123"

6. 流式调用优势

用户体验优势:

  • 实时反馈:用户可以看到AI的思考过程
  • 透明性:工具调用过程完全透明
  • 交互性:用户可以实时了解AI在做什么
  • 可中断:用户可以随时中断长时间的操作

技术优势:

  • 资源友好:不需要等待完整响应
  • 错误处理:可以实时处理错误
  • 可扩展:支持复杂的工具链调用
  • 监控友好:便于监控和调试

总结

学习路径回顾

通过本文档,我们从简单到复杂逐步学习了LangChain4j的各个核心功能:

  1. 环境准备与依赖安装 - 搭建基础开发环境
  2. 简单流式调用 - 实现基础的AI对话功能
  3. 记忆对话功能 - 让AI记住对话历史
  4. 工具调用 - 让AI能够调用外部工具
  5. 工具流式返回调用结果 - 实时展示工具调用过程
  6. Redis流式调用 - 分布式环境下的高性能实现

核心技术要点

1. 流式调用架构
用户请求 → AI服务 → 流式响应 → 前端实时展示↓
Redis记忆存储 ← 工具调用 ← 思考过程
2. 关键技术组件
  • StreamingChatModel: 流式聊天模型
  • AiServices: AI服务构建器
  • MessageWindowChatMemory: 记忆管理
  • RedisChatMemoryStore: Redis记忆存储
  • @Tool: 工具注解
  • Flux: 响应式流处理
3. 数据流转过程
  1. 用户发送消息
  2. AI开始思考(onPartialThinking)
  3. 调用工具(beforeToolExecution → onToolExecuted)
  4. 生成回答(onPartialResponse)
  5. 完成响应(onCompleteResponse)
  6. 存储到Redis记忆

最佳实践建议

1. 性能优化
  • 合理设置记忆消息数量限制
  • 使用Redis连接池
  • 设置合适的TTL过期时间
  • 监控Redis内存使用
2. 错误处理
  • 实现完善的异常捕获
  • 提供友好的错误提示
  • 记录详细的错误日志
  • 实现重试机制

扩展方向

1. 功能扩展
  • 支持更多AI模型
  • 实现工具链调用
  • 添加文件上传处理
  • 支持多模态输入
2. 架构扩展
  • 微服务架构
  • 消息队列集成
  • 负载均衡
  • 容器化部署
3. 业务扩展
  • 多租户支持
  • 用户权限管理
  • 对话模板
  • 知识库集成
2. 流式响应中断
// 处理客户端断开连接
.onCancel(() -> {log.info("客户端取消连接");// 清理资源
})
3. 工具调用超时
// 设置工具调用超时
@Tool("长时间运行的工具")
@Timeout(30) // 30秒超时
public String longRunningTool() {// 工具实现
}

项目结构建议

src/main/java/com/xinggui/xinggui_code_langchain/
├── ai/
│   ├── chatModel/          # 聊天模型配置
│   ├── service/            # AI服务
│   ├── tool/               # 工具类
│   └── response/           # 响应实体
├── config/                 # 配置类
├── controller/             # 控制器
├── service/                # 业务服务
└── utils/                  # 工具类

学习建议

  1. 循序渐进: 从简单功能开始,逐步添加复杂特性
  2. 实践为主: 多写代码,多测试,多调试
  3. 关注性能: 注意内存使用和响应时间
  4. 文档记录: 记录遇到的问题和解决方案
  5. 社区参与: 关注LangChain4j官方文档和社区

结语

LangChain4j是一个功能强大的Java AI应用开发框架,通过本文档的学习,你应该已经掌握了:

  • 基础的流式AI对话实现
  • 记忆功能的配置和使用
  • 工具调用的开发和集成
  • Redis在AI应用中的应用
  • 完整的生产级架构设计

继续深入学习,探索更多高级特性,构建更强大的AI应用!



文章转载自:

http://rSVButK2.tntgc.cn
http://U6s5nIvN.tntgc.cn
http://HE9HdweR.tntgc.cn
http://iM79dCQi.tntgc.cn
http://7AsdPMuh.tntgc.cn
http://B6axYX8E.tntgc.cn
http://BiDCG3tV.tntgc.cn
http://7HT1vEkj.tntgc.cn
http://7YqDrIbI.tntgc.cn
http://dxXA0lY7.tntgc.cn
http://ANFE13Fz.tntgc.cn
http://DZVxBYcW.tntgc.cn
http://US7n1fZk.tntgc.cn
http://v6Yzi6Bl.tntgc.cn
http://QUXbpEOi.tntgc.cn
http://gvbKlFuY.tntgc.cn
http://zoYnTwor.tntgc.cn
http://UulBbiEL.tntgc.cn
http://8OnYZvAt.tntgc.cn
http://x6xrqvZR.tntgc.cn
http://tHym4Jhj.tntgc.cn
http://bcgOSZAN.tntgc.cn
http://MzCHI4xn.tntgc.cn
http://Qa2JySTT.tntgc.cn
http://tIeQRyeh.tntgc.cn
http://Lh6Sw874.tntgc.cn
http://X3GtM49b.tntgc.cn
http://QoXxXjRv.tntgc.cn
http://ltrjEm5R.tntgc.cn
http://1EQe1ery.tntgc.cn
http://www.dtcms.com/a/380749.html

相关文章:

  • Django ORM 模型
  • 【SpringBoot】——原理篇
  • 机器人防爆的详细讲解
  • 【Vue3】06-利用setup编写vue(1)
  • 单序列双指针
  • Linux中进程和线程常用的API详解
  • 【AI论文】多模态大型语言模型的视觉表征对齐
  • php学习(第四天)
  • Vue中使用keep-alive实现页面前进刷新、后退缓存的完整方案
  • Jenkins运维之路(Jenkins流水线改造Day02-1-容器项目)
  • Netty从0到1系列之Netty逻辑架构【上】
  • com.google.common.cache实现本地缓存
  • LeetCode 面试经典 150 题之最后一个单词的长度:逆向遍历高效解法
  • 详解JWT
  • Spring Boot 分布式事务常见问题:Seata、XA 与本地消息表对比
  • 如何在Hugging Face中下载全部文件?
  • AI之VideoTool:AI-Video-Transcriber​​​​​​​的简介、安装和使用方法、案例应用之详细攻略
  • Qt6实现了一个打地鼠小游戏,支持AI自动打地鼠
  • Chapter5—抽象工厂模式
  • WebSocket连接状态监控与自动重连实现
  • 目标计数论文阅读(1)Class-Agnostic Counting
  • LVGL移植2048小游戏全攻略
  • 大模型系列——ChatBI重构企业知识库
  • DEM(数字高程模型)详解
  • 软考 系统架构设计师系列知识点之杂项集萃(144)
  • R语言生物群落(生态)数据统计分析与绘图实践技术应用
  • DPO 深度解析:从公式到工程,从偏好数据到可复用训练管线
  • 今天继续学习Linux系统中shell脚本
  • 开源端到端训练多模态大模型LLaVA 深度拆解
  • 周志华《机器学习导论》第10章 降维与度量学习