当 ChatGPT 横空出世,Python 开发者狂欢时,Java 开发者只能望洋兴叹?不,LangChain4j 来了!

前言

2023 年,大语言模型(LLM)席卷全球。Python 生态凭借 LangChain 框架迅速占领了 AI 应用开发的高地。而作为企业级开发主力军的 Java 开发者,却面临一个尴尬的问题:

如何在 Java 项目中集成 LLM 能力?

答案就是 LangChain4j —— Java 生态的 LLM 应用开发框架。

本文将从企业面临的四个真实问题出发,带你从零掌握 LangChain4j,并实现四个完整的企业级应用场景。


一、企业面临的真实问题

在 AI 时代,企业面临以下挑战:

1.1 知识管理困境

员工每天花 2.5 小时搜索信息
            ↓
企业文档散落在各个系统
            ↓
新员工培训周期长
            ↓
重复回答相同问题

问题:如何让 AI 理解企业内部文档,成为知识助手?

1.2 客服成本高企

人工客服成本高
            ↓
7×24 小时难以保障
            ↓
回答质量参差不齐
            ↓
高峰期响应慢

问题:如何构建智能客服,自动处理 80% 的常见问题?

1.3 非结构化数据处理

合同、发票、报告堆积如山
            ↓
人工提取信息效率低
            ↓
容易出错
            ↓
无法批量处理

问题:如何自动解析文档,提取关键信息?

1.4 数据查询门槛

业务人员想看数据
            ↓
不会写 SQL
            ↓
依赖开发人员
            ↓
需求响应慢

问题:如何让业务人员用自然语言查询数据库?

LangChain4j 就是解决这些问题的利器!


二、LangChain4j 是什么

2.1 定义

LangChain4j 是一个 Java 框架,用于简化 LLM(大语言模型)应用开发。它是 Python LangChain 的 Java 实现,但并非简单移植,而是充分利用 Java 生态的优势。

┌─────────────────────────────────────────────────────────┐
│                    LLM 应用开发框架                       │
├────────────────────────┬────────────────────────────────┤
│     Python LangChain   │        LangChain4j (Java)     │
├────────────────────────┼────────────────────────────────┤
│  ✓ 生态成熟            │  ✓ 企业级稳定性               │
│  ✓ 社区活跃            │  ✓ Spring Boot 集成           │
│  ✓ 快速原型            │  ✓ 类型安全                   │
│  ✗ 企业应用部署复杂    │  ✓ 成熟的工程实践             │
│  ✗ 运行时性能          │  ✓ 与现有 Java 系统无缝集成   │
└────────────────────────┴────────────────────────────────┘

2.2 核心特性

1. 统一的 LLM 接口

// 一套代码,支持多种 LLM
ChatLanguageModel model = OpenAiChatModel.builder()
    .apiKey("your-api-key")
    .modelName("gpt-4")
    .build();
​
// 切换到其他模型,只需改一行
// ChatLanguageModel model = OllamaChatModel.builder()
//     .modelName("llama2")
//     .build();

2. RAG(检索增强生成)支持

// 让 LLM 能够访问企业私有数据
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();
​
// 文档加载 → 向量化 → 存储 → 检索

3. 对话记忆管理

// 自动管理多轮对话上下文
ChatMemory chatMessageWindow = MessageWindowChatMemory.withMaxMessages(10);

4. 工具调用(Function Calling)

// 让 LLM 调用外部工具
@Tool("获取当前天气")
public String getWeather(@P("城市名称") String city) {
    return weatherService.getWeather(city);
}

5. AI Services(声明式 AI 接口)

// 像定义 Feign 客户端一样定义 AI 接口
@AiService
public interface Assistant {
    @SystemMessage("你是一个 helpful 的助手")
    String chat(@MemoryId String sessionId, @UserMessage String message);
}

2.3 支持的 LLM 提供商

提供商 模型示例 特点
OpenAI GPT-4, GPT-3.5 最强大,成本较高
Azure OpenAI GPT-4, GPT-3.5 企业级,合规性好
Ollama Llama2, Mistral 本地部署,免费
Hugging Face 各种开源模型 丰富的模型库
Google Gemini 多模态支持
Anthropic Claude 长上下文
国产模型 通义千问、文心一言 中文优化

2.4 与 Spring Boot 的完美集成

<!-- LangChain4j Spring Boot Starter -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
    <version>0.25.0</version>
</dependency>
# application.yml 配置
langchain4j:
  open-ai:
    chat-model:
      api-key: ${OPENAI_API_KEY}
      model-name: gpt-4
      temperature: 0.7
      max-tokens: 2000

三、环境搭建

3.1 创建项目

使用 Spring Initializr 创建项目,或手动添加依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
​
    <modelVersion>4.0.0</modelVersion>
​
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
    </parent>
​
    <groupId>com.example</groupId>
    <artifactId>langchain4j-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>langchain4j-demo</name>
    <description>LangChain4j 企业级实战示例</description>
​
    <properties>
        <java.version>17</java.version>
        <langchain4j.version>0.25.0</langchain4j.version>
    </properties>
​
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
​
        <!-- LangChain4j Core -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
​
        <!-- LangChain4j Spring Boot Starter -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-spring-boot-starter</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
​
        <!-- OpenAI 集成 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
​
        <!-- 本地嵌入模型(用于 RAG) -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
​
        <!-- 文档解析器 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-document-parser-apache-tika</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
​
        <!-- 数据库(用于示例) -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
​
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
​
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.2 配置文件

# application.yml
server:
  port: 8080
​
spring:
  application:
    name: langchain4j-demo
  datasource:
    url: jdbc:h2:mem:demo
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
​
# LangChain4j 配置
langchain4j:
  open-ai:
    chat-model:
      api-key: ${OPENAI_API_KEY:sk-xxx}
      model-name: gpt-3.5-turbo
      temperature: 0.7
      max-tokens: 2000
      log-requests: true
      log-responses: true
​
# 自定义配置
app:
  knowledge-base:
    document-path: classpath:documents/
    chunk-size: 500
    chunk-overlap: 50

3.3 项目结构

src/main/java/com/example/langchain4j/
├── Langchain4jDemoApplication.java
├── config/
│   └── LangChain4jConfig.java
├── controller/
│   ├── KnowledgeController.java
│   ├── ChatbotController.java
│   ├── DocumentController.java
│   └── DataQueryController.java
├── service/
│   ├── KnowledgeService.java
│   ├── ChatbotService.java
│   ├── DocumentService.java
│   └── DataQueryService.java
├── model/
│   ├── ChatMessage.java
│   └── QueryResult.java
├── agent/
│   ├── Assistant.java
│   └── tools/
│       ├── WeatherTool.java
│       └── DatabaseTool.java
└── rag/
    ├── DocumentLoader.java
    └── Retriever.java
​
src/main/resources/
├── application.yml
└── documents/
    ├── product-manual.pdf
    ├── faq.txt
    └── company-policy.docx

四、问题 1:如何让 AI 理解企业文档?

4.1 痛点分析

大语言模型(如 GPT-4)的知识截止到训练时间点,而且 不知道企业内部的私有数据

用户问:我们公司的请假流程是什么?
​
LLM 答:一般来说,请假流程是...(胡说八道)

解决方案:RAG(检索增强生成)

┌─────────────────────────────────────────────────────────┐
│                    RAG 工作流程                          │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   用户提问                                              │
│       ↓                                                 │
│   问题向量化                                            │
│       ↓                                                 │
│   向量检索(找到相关文档片段)                            │
│       ↓                                                 │
│   将文档片段 + 问题 → 发送给 LLM                        │
│       ↓                                                 │
│   LLM 基于文档内容生成准确回答                           │
│                                                         │
└─────────────────────────────────────────────────────────┘

4.2 RAG 实现

1. 定义 AI 服务接口

@AiService
public interface KnowledgeAssistant {
​
    @SystemMessage("""
        你是一个企业知识库助手。请根据提供的上下文信息回答问题。
        如果上下文中没有相关信息,请明确告知用户,不要编造答案。
        回答要简洁、准确、专业。
        """)
    String answer(@MemoryId String sessionId, @V("question") String question);
}

2. 配置 RAG 组件

@Configuration
public class RagConfig {
​
    @Value("${app.knowledge-base.document-path}")
    private Resource documentPath;
​
    @Value("${app.knowledge-base.chunk-size:500}")
    private int chunkSize;
​
    @Value("${app.knowledge-base.chunk-overlap:50}")
    private int chunkOverlap;
​
    @Bean
    public EmbeddingModel embeddingModel() {
        return new AllMiniLmL6V2EmbeddingModel();
    }
​
    @Bean
    public EmbeddingStore<TextSegment> embeddingStore() {
        return new InMemoryEmbeddingStore<>();
    }
​
    @Bean
    public DocumentParser documentParser() {
        return new ApacheTikaDocumentParser();
    }
​
    @Bean
    public EmbeddingStoreIngestor embeddingStoreIngestor(
            EmbeddingModel embeddingModel,
            EmbeddingStore<TextSegment> embeddingStore) {
​
        return EmbeddingStoreIngestor.builder()
            .documentSplitter(DocumentSplitters.recursive(chunkSize, chunkOverlap))
            .embeddingModel(embeddingModel)
            .embeddingStore(embeddingStore)
            .build();
    }
​
    @Bean
    public ContentRetriever contentRetriever(
            EmbeddingModel embeddingModel,
            EmbeddingStore<TextSegment> embeddingStore) {
​
        return EmbeddingStoreContentRetriever.builder()
            .embeddingStore(embeddingStore)
            .embeddingModel(embeddingModel)
            .maxResults(5)
            .minScore(0.7)
            .build();
    }
}

3. 文档加载服务

@Service
@Slf4j
public class KnowledgeService {
​
    private final EmbeddingStoreIngestor ingestor;
    private final DocumentParser documentParser;
    private final ResourceLoader resourceLoader;
​
    @Value("${app.knowledge-base.document-path}")
    private String documentPath;
​
    public KnowledgeService(EmbeddingStoreIngestor ingestor,
                           DocumentParser documentParser,
                           ResourceLoader resourceLoader) {
        this.ingestor = ingestor;
        this.documentParser = documentParser;
        this.resourceLoader = resourceLoader;
    }
​
    /**
     * 加载单个文档到知识库
     */
    public void loadDocument(String filePath) throws IOException {
        log.info("加载文档: {}", filePath);
​
        Resource resource = resourceLoader.getResource(filePath);
        Document document = documentParser.parse(resource.getInputStream());
​
        // 设置文档元数据
        document.metadata().put("source", filePath);
        document.metadata().put("loadTime", LocalDateTime.now().toString());
​
        ingestor.ingest(document);
        log.info("文档加载完成: {}", filePath);
    }
​
    /**
     * 批量加载目录下所有文档
     */
    public int loadAllDocuments() throws IOException {
        Resource directory = resourceLoader.getResource(documentPath);
        File dir = directory.getFile();
​
        if (!dir.exists() || !dir.isDirectory()) {
            throw new IllegalArgumentException("文档目录不存在: " + documentPath);
        }
​
        int count = 0;
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (isSupportedFile(file.getName())) {
                    loadDocument("file:" + file.getAbsolutePath());
                    count++;
                }
            }
        }
​
        log.info("共加载 {} 个文档", count);
        return count;
    }
​
    private boolean isSupportedFile(String filename) {
        return filename.endsWith(".pdf") ||
               filename.endsWith(".docx") ||
               filename.endsWith(".txt") ||
               filename.endsWith(".md");
    }
}

4. 知识问答控制器

@RestController
@RequestMapping("/api/knowledge")
@RequiredArgsConstructor
public class KnowledgeController {
​
    private final KnowledgeAssistant assistant;
    private final KnowledgeService knowledgeService;
​
    /**
     * 知识库问答
     */
    @PostMapping("/ask")
    public ResponseEntity<Map<String, String>> ask(
            @RequestParam String sessionId,
            @RequestParam String question) {
​
        String answer = assistant.answer(sessionId, question);
​
        return ResponseEntity.ok(Map.of(
            "question", question,
            "answer", answer,
            "sessionId", sessionId
        ));
    }
​
    /**
     * 加载文档到知识库
     */
    @PostMapping("/documents/load")
    public ResponseEntity<Map<String, Object>> loadDocuments() {
        try {
            int count = knowledgeService.loadAllDocuments();
            return ResponseEntity.ok(Map.of(
                "success", true,
                "message", "成功加载 " + count + " 个文档"
            ));
        } catch (IOException e) {
            return ResponseEntity.badRequest().body(Map.of(
                "success", false,
                "message", "加载失败: " + e.getMessage()
            ));
        }
    }
​
    /**
     * 加载单个文档
     */
    @PostMapping("/documents/load/{filename}")
    public ResponseEntity<Map<String, Object>> loadDocument(
            @PathVariable String filename) {
        try {
            String filePath = "classpath:documents/" + filename;
            knowledgeService.loadDocument(filePath);
            return ResponseEntity.ok(Map.of(
                "success", true,
                "message", "文档加载成功: " + filename
            ));
        } catch (IOException e) {
            return ResponseEntity.badRequest().body(Map.of(
                "success", false,
                "message", "加载失败: " + e.getMessage()
            ));
        }
    }
}

5. 测试 RAG

# 1. 先加载文档
curl -X POST http://localhost:8080/api/knowledge/documents/load
​
# 2. 提问
curl -X POST "http://localhost:8080/api/knowledge/ask?sessionId=user1&question=公司的年假政策是什么"
​
# 返回:
# {
#   "question": "公司的年假政策是什么",
#   "answer": "根据公司规定,员工入职满一年后享有5天年假,每增加一年工龄增加1天,最多15天...",
#   "sessionId": "user1"
# }

4.3 高级 RAG:混合检索

@Configuration
public class AdvancedRagConfig {
​
    @Bean
    public ContentRetriever hybridRetriever(
            EmbeddingModel embeddingModel,
            EmbeddingStore<TextSegment> embeddingStore) {
​
        // 向量检索
        EmbeddingStoreContentRetriever embeddingRetriever =
            EmbeddingStoreContentRetriever.builder()
                .embeddingStore(embeddingStore)
                .embeddingModel(embeddingModel)
                .maxResults(3)
                .minScore(0.6)
                .build();
​
        // 关键词检索(需要额外实现)
        // ContentRetriever keywordRetriever = ...;
​
        // 混合检索
        return EmbeddingStoreContentRetriever.builder()
            .embeddingStore(embeddingStore)
            .embeddingModel(embeddingModel)
            .maxResults(5)
            .minScore(0.5)
            .build();
    }
}

五、问题 2:如何构建智能客服?

5.1 痛点分析

传统客服面临的问题:

  • 重复回答相同问题

  • 无法 7×24 小时服务

  • 多轮对话上下文丢失

  • 无法调用外部系统

解决方案:Memory + Tools + Agent

5.2 智能客服实现

1. 定义客服 AI 服务

@AiService
public interface CustomerServiceAgent {
​
    @SystemMessage("""
        你是「小智」,一个专业的客服助手。
​
        你的职责:
        1. 回答用户关于产品、服务、订单的常见问题
        2. 帮助用户查询订单状态、物流信息
        3. 处理简单的售后问题
        4. 对于复杂问题,建议用户联系人工客服
​
        规则:
        - 回答要友好、专业、简洁
        - 如果不确定答案,不要编造,建议用户联系人工客服
        - 可以调用工具查询订单、天气等实时信息
        - 记住用户的对话历史,提供连贯的服务
​
        人工客服热线:400-123-4567
        """)
    String chat(@MemoryId String sessionId, @UserMessage String message);
}

2. 客服工具定义

@Component
@Slf4j
public class CustomerServiceTools {
​
    @Autowired
    private OrderRepository orderRepository;
​
    @Autowired
    private ProductRepository productRepository;
​
    @Tool("查询订单状态。参数:订单号")
    public String queryOrderStatus(@P("订单号") String orderNumber) {
        log.info("查询订单状态: {}", orderNumber);
​
        return orderRepository.findByOrderNumber(orderNumber)
            .map(order -> String.format(
                "订单号:%s\n状态:%s\n下单时间:%s\n预计送达:%s",
                order.getOrderNumber(),
                getStatusName(order.getStatus()),
                order.getCreateTime(),
                order.getEstimatedDelivery()
            ))
            .orElse("未找到订单号为 " + orderNumber + " 的订单,请检查订单号是否正确");
    }
​
    @Tool("查询产品信息。参数:产品名称或关键词")
    public String queryProduct(@P("产品名称或关键词") String keyword) {
        log.info("查询产品信息: {}", keyword);
​
        List<Product> products = productRepository.findByNameContaining(keyword);
​
        if (products.isEmpty()) {
            return "未找到相关产品";
        }
​
        StringBuilder sb = new StringBuilder("为您找到以下产品:\n");
        for (Product product : products) {
            sb.append(String.format("- %s:¥%.2f,库存 %d 件\n",
                product.getName(),
                product.getPrice(),
                product.getStock()));
        }
        return sb.toString();
    }
​
    @Tool("查询物流信息。参数:订单号")
    public String queryLogistics(@P("订单号") String orderNumber) {
        log.info("查询物流信息: {}", orderNumber);
​
        // 模拟物流查询
        return String.format(
            "订单 %s 的物流信息:\n" +
            "当前位置:北京转运中心\n" +
            "最新状态:已发出,预计明天送达\n" +
            "快递单号:SF1234567890",
            orderNumber
        );
    }
​
    @Tool("创建工单。参数:问题描述、用户ID")
    public String createTicket(@P("问题描述") String description,
                              @P("用户ID") String userId) {
        log.info("创建工单: userId={}, description={}", userId, description);
​
        String ticketId = "TK" + System.currentTimeMillis();
​
        // 保存工单到数据库...
        return String.format(
            "工单已创建!\n工单号:%s\n问题:%s\n我们会尽快处理,预计24小时内回复。",
            ticketId,
            description
        );
    }
​
    private String getStatusName(OrderStatus status) {
        return switch (status) {
            case PENDING_PAYMENT -> "待付款";
            case PAID -> "已付款";
            case SHIPPED -> "已发货";
            case DELIVERED -> "已送达";
            case COMPLETED -> "已完成";
            case CANCELLED -> "已取消";
        };
    }
}

3. 实体类定义

@Entity
@Table(name = "orders")
@Data
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
​
    @Column(unique = true)
    private String orderNumber;
​
    private Long userId;
    private String productName;
    private Integer quantity;
    private BigDecimal totalAmount;
​
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
​
    private LocalDateTime createTime;
    private String estimatedDelivery;
}
​
public enum OrderStatus {
    PENDING_PAYMENT, PAID, SHIPPED, DELIVERED, COMPLETED, CANCELLED
}
​
@Entity
@Table(name = "products")
@Data
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
​
    private String name;
    private String description;
    private BigDecimal price;
    private Integer stock;
}

4. 客服控制器

@RestController
@RequestMapping("/api/chatbot")
@RequiredArgsConstructor
public class ChatbotController {
​
    private final CustomerServiceAgent agent;
​
    /**
     * 与客服机器人对话
     */
    @PostMapping("/chat")
    public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) {
        String answer = agent.chat(request.getSessionId(), request.getMessage());
​
        return ResponseEntity.ok(new ChatResponse(
            request.getSessionId(),
            request.getMessage(),
            answer
        ));
    }
}
​
@Data
@AllArgsConstructor
@NoArgsConstructor
class ChatRequest {
    private String sessionId;
    private String message;
}
​
@Data
@AllArgsConstructor
class ChatResponse {
    private String sessionId;
    private String question;
    private String answer;
}

5. 测试智能客服

# 查询订单
curl -X POST http://localhost:8080/api/chatbot/chat \
  -H "Content-Type: application/json" \
  -d '{"sessionId": "user1", "message": "帮我查一下订单 ORD20240115001 的状态"}'
​
# 返回:
# {
#   "sessionId": "user1",
#   "question": "帮我查一下订单 ORD20240115001 的状态",
#   "answer": "您的订单 ORD20240115001 当前状态为:已发货\n下单时间:2024-01-15\n预计送达:2024-01-18"
# }
​
# 查询产品
curl -X POST http://localhost:8080/api/chatbot/chat \
  -H "Content-Type: application/json" \
  -d '{"sessionId": "user1", "message": "你们有蓝牙耳机吗?"}'
​
# 多轮对话(上下文记忆)
curl -X POST http://localhost:8080/api/chatbot/chat \
  -H "Content-Type: application/json" \
  -d '{"sessionId": "user1", "message": "刚才那个订单的物流信息呢?"}'

5.3 对话记忆配置

@Configuration
public class MemoryConfig {
​
    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        return sessionId -> MessageWindowChatMemory.builder()
            .id(sessionId)
            .maxMessages(20)  // 保留最近20条消息
            .build();
    }
}

六、问题 3:如何处理非结构化数据?

6.1 痛点分析

企业中存在大量非结构化数据:

  • PDF 合同

  • Word 报告

  • 扫描件图片

  • 网页内容

解决方案:Document Loaders + Parsers + AI 提取

6.2 文档处理服务

1. 定义文档处理接口

@Data
@AllArgsConstructor
public class DocumentInfo {
    private String filename;
    private String content;
    private String summary;
    private Map<String, String> extractedFields;
    private LocalDateTime processedTime;
}
​
public interface DocumentProcessor {
    DocumentInfo process(String filePath) throws IOException;
    DocumentInfo processWithExtraction(String filePath,
                                       Map<String, String> extractionRules) throws IOException;
}

2. 实现文档处理服务

@Service
@Slf4j
public class DocumentService implements DocumentProcessor {
​
    private final DocumentParser documentParser;
    private final ChatLanguageModel chatModel;
    private final ResourceLoader resourceLoader;
​
    public DocumentService(DocumentParser documentParser,
                          ChatLanguageModel chatModel,
                          ResourceLoader resourceLoader) {
        this.documentParser = documentParser;
        this.chatModel = chatModel;
        this.resourceLoader = resourceLoader;
    }
​
    @Override
    public DocumentInfo process(String filePath) throws IOException {
        log.info("处理文档: {}", filePath);
​
        // 1. 解析文档
        Resource resource = resourceLoader.getResource(filePath);
        Document document = documentParser.parse(resource.getInputStream());
        String content = document.text();
​
        // 2. 生成摘要
        String summary = generateSummary(content);
​
        return new DocumentInfo(
            getFilename(filePath),
            content,
            summary,
            new HashMap<>(),
            LocalDateTime.now()
        );
    }
​
    @Override
    public DocumentInfo processWithExtraction(String filePath,
                                              Map<String, String> extractionRules) throws IOException {
        log.info("处理文档并提取信息: {}", filePath);
​
        // 1. 解析文档
        Resource resource = resourceLoader.getResource(filePath);
        Document document = documentParser.parse(resource.getInputStream());
        String content = document.text();
​
        // 2. 生成摘要
        String summary = generateSummary(content);
​
        // 3. 提取结构化信息
        Map<String, String> extractedFields = extractFields(content, extractionRules);
​
        return new DocumentInfo(
            getFilename(filePath),
            content,
            summary,
            extractedFields,
            LocalDateTime.now()
        );
    }
​
    /**
     * 使用 AI 生成文档摘要
     */
    private String generateSummary(String content) {
        // 截取前3000个字符(避免超出 token 限制)
        String truncatedContent = content.length() > 3000
            ? content.substring(0, 3000) + "..."
            : content;
​
        String prompt = String.format("""
            请为以下文档生成一个简洁的摘要(不超过200字):
​
            %s
            """, truncatedContent);
​
        return chatModel.generate(prompt);
    }
​
    /**
     * 使用 AI 提取结构化信息
     */
    private Map<String, String> extractFields(String content,
                                              Map<String, String> extractionRules) {
        StringBuilder rulesText = new StringBuilder();
        extractionRules.forEach((field, description) ->
            rulesText.append(String.format("- %s:%s\n", field, description))
        );
​
        String prompt = String.format("""
            请从以下文档中提取指定信息,以 JSON 格式返回:
​
            提取规则:
            %s
​
            文档内容:
            %s
​
            请返回纯 JSON,不要添加其他说明。
            """, rulesText, content.substring(0, Math.min(content.length(), 3000)));
​
        String response = chatModel.generate(prompt);
​
        // 解析 JSON
        try {
            return new ObjectMapper().readValue(response, new TypeReference<>() {});
        } catch (Exception e) {
            log.warn("解析提取结果失败", e);
            return new HashMap<>();
        }
    }
​
    private String getFilename(String filePath) {
        return filePath.substring(filePath.lastIndexOf("/") + 1);
    }
}

3. 文档控制器

@RestController
@RequestMapping("/api/documents")
@RequiredArgsConstructor
public class DocumentController {
​
    private final DocumentService documentService;
​
    /**
     * 处理文档并生成摘要
     */
    @PostMapping("/process")
    public ResponseEntity<DocumentInfo> processDocument(
            @RequestParam String filePath) {
        try {
            DocumentInfo info = documentService.process(filePath);
            return ResponseEntity.ok(info);
        } catch (IOException e) {
            return ResponseEntity.badRequest().build();
        }
    }
​
    /**
     * 处理文档并提取信息(如发票、合同)
     */
    @PostMapping("/extract")
    public ResponseEntity<DocumentInfo> extractFromDocument(
            @RequestParam String filePath,
            @RequestBody Map<String, String> extractionRules) {
        try {
            DocumentInfo info = documentService.processWithExtraction(filePath, extractionRules);
            return ResponseEntity.ok(info);
        } catch (IOException e) {
            return ResponseEntity.badRequest().build();
        }
    }
}

4. 测试文档处理

# 处理 PDF 文档
curl -X POST "http://localhost:8080/api/documents/process?filePath=classpath:documents/contract.pdf"
​
# 提取发票信息
curl -X POST "http://localhost:8080/api/documents/extract?filePath=classpath:documents/invoice.pdf" \
  -H "Content-Type: application/json" \
  -d '{
    "发票号码": "发票的唯一编号",
    "开票日期": "发票开具的日期",
    "金额": "发票总金额",
    "购买方": "购买方名称",
    "销售方": "销售方名称"
  }'

6.3 批量处理流水线

@Service
@Slf4j
public class DocumentPipeline {
​
    private final DocumentService documentService;
    private final KnowledgeService knowledgeService;
​
    /**
     * 批量处理文档并导入知识库
     */
    public PipelineResult processAndIndex(String directoryPath) throws IOException {
        PipelineResult result = new PipelineResult();
​
        Resource directory = new ClassPathResource(directoryPath);
        File dir = directory.getFile();
​
        File[] files = dir.listFiles();
        if (files == null) return result;
​
        for (File file : files) {
            try {
                // 1. 处理文档
                DocumentInfo info = documentService.process("file:" + file.getAbsolutePath());
                result.addProcessed(info.getFilename());
​
                // 2. 导入知识库
                knowledgeService.loadDocument("file:" + file.getAbsolutePath());
                result.addIndexed(info.getFilename());
​
                log.info("文档处理完成: {}", file.getName());
            } catch (Exception e) {
                result.addFailed(file.getName(), e.getMessage());
                log.error("文档处理失败: {}", file.getName(), e);
            }
        }
​
        return result;
    }
}
​
@Data
class PipelineResult {
    private List<String> processed = new ArrayList<>();
    private List<String> indexed = new ArrayList<>();
    private Map<String, String> failed = new HashMap<>();
​
    public void addProcessed(String filename) { processed.add(filename); }
    public void addIndexed(String filename) { indexed.add(filename); }
    public void addFailed(String filename, String error) { failed.put(filename, error); }
}

七、问题 4:如何用自然语言查数据库?

7.1 痛点分析

业务人员想看数据,但不会写 SQL:

业务人员:我想看上个月销售额最高的10个产品
开发人员:好的,我帮你写个SQL...
(等待3天)

解决方案:Text-to-SQL Agent

7.2 SQL Agent 实现

1. 定义数据查询 AI 服务

@AiService
public interface DataQueryAgent {
​
    @SystemMessage("""
        你是一个数据分析助手。你可以帮助用户用自然语言查询数据库。
​
        数据库表结构:
        - orders:订单表(id, order_number, user_id, product_id, quantity, amount, status, create_time)
        - products:产品表(id, name, category, price, stock)
        - users:用户表(id, username, email, register_time)
​
        规则:
        1. 将用户的自然语言问题转换为 SQL 查询
        2. 执行 SQL 并获取结果
        3. 将结果转换为易读的自然语言回答
        4. 如果用户的查询可能导致性能问题(如全表扫描),请优化 SQL
        5. 对于敏感数据,请提醒用户权限限制
​
        你可以使用以下工具:
        - executeSql:执行 SQL 查询
        - getTableSchema:获取表结构
        """)
    String query(@MemoryId String sessionId, @UserMessage String question);
}

2. 数据库工具

@Component
@Slf4j
public class DatabaseTools {
​
    @Autowired
    private JdbcTemplate jdbcTemplate;
​
    @Tool("执行 SQL 查询并返回结果")
    public String executeSql(@P("SQL 查询语句") String sql) {
        log.info("执行 SQL: {}", sql);
​
        // 安全检查
        if (isDangerousSql(sql)) {
            return "错误:不允许执行危险的 SQL 语句(如 DROP, DELETE, UPDATE, INSERT)";
        }
​
        try {
            List<Map<String, Object>> results = jdbcTemplate.queryForList(sql);
​
            if (results.isEmpty()) {
                return "查询结果为空";
            }
​
            // 格式化结果
            StringBuilder sb = new StringBuilder();
            sb.append("查询返回 ").append(results.size()).append(" 条记录:\n\n");
​
            // 表头
            Set<String> columns = results.get(0).keySet();
            sb.append(String.join(" | ", columns)).append("\n");
            sb.append("-".repeat(columns.size() * 15)).append("\n");
​
            // 数据行(最多显示50行)
            int displayCount = Math.min(results.size(), 50);
            for (int i = 0; i < displayCount; i++) {
                Map<String, Object> row = results.get(i);
                List<String> values = columns.stream()
                    .map(col -> String.valueOf(row.get(col)))
                    .toList();
                sb.append(String.join(" | ", values)).append("\n");
            }
​
            if (results.size() > 50) {
                sb.append("... 还有 ").append(results.size() - 50).append(" 条记录");
            }
​
            return sb.toString();
        } catch (Exception e) {
            log.error("SQL 执行失败", e);
            return "SQL 执行失败: " + e.getMessage();
        }
    }
​
    @Tool("获取指定表的结构信息")
    public String getTableSchema(@P("表名") String tableName) {
        log.info("获取表结构: {}", tableName);
​
        try {
            List<Map<String, Object>> columns = jdbcTemplate.queryForList(
                "SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_COMMENT " +
                "FROM INFORMATION_SCHEMA.COLUMNS " +
                "WHERE TABLE_NAME = ?",
                tableName.toUpperCase()
            );
​
            if (columns.isEmpty()) {
                return "表 " + tableName + " 不存在";
            }
​
            StringBuilder sb = new StringBuilder();
            sb.append("表 ").append(tableName).append(" 的结构:\n\n");
​
            for (Map<String, Object> col : columns) {
                sb.append(String.format("- %s (%s): %s %s\n",
                    col.get("COLUMN_NAME"),
                    col.get("DATA_TYPE"),
                    "YES".equals(col.get("IS_NULLABLE")) ? "可空" : "非空",
                    col.get("COLUMN_COMMENT") != null ? col.get("COLUMN_COMMENT") : ""
                ));
            }
​
            return sb.toString();
        } catch (Exception e) {
            return "获取表结构失败: " + e.getMessage();
        }
    }
​
    private boolean isDangerousSql(String sql) {
        String upperSql = sql.toUpperCase().trim();
        return upperSql.startsWith("DROP") ||
               upperSql.startsWith("DELETE") ||
               upperSql.startsWith("UPDATE") ||
               upperSql.startsWith("INSERT") ||
               upperSql.startsWith("ALTER") ||
               upperSql.contains("TRUNCATE");
    }
}

3. 数据查询控制器

@RestController
@RequestMapping("/api/data-query")
@RequiredArgsConstructor
public class DataQueryController {
​
    private final DataQueryAgent agent;
​
    /**
     * 自然语言查询数据
     */
    @PostMapping("/query")
    public ResponseEntity<QueryResponse> query(@RequestBody QueryRequest request) {
        String answer = agent.query(request.getSessionId(), request.getQuestion());
​
        return ResponseEntity.ok(new QueryResponse(
            request.getSessionId(),
            request.getQuestion(),
            answer
        ));
    }
}
​
@Data
class QueryRequest {
    private String sessionId;
    private String question;
}
​
@Data
@AllArgsConstructor
class QueryResponse {
    private String sessionId;
    private String question;
    private String answer;
}

4. 测试自然语言查询

# 查询销售数据
curl -X POST http://localhost:8080/api/data-query/query \
  -H "Content-Type: application/json" \
  -d '{"sessionId": "analyst1", "question": "上个月销售额最高的10个产品是什么?"}'
​
# 返回:
# {
#   "sessionId": "analyst1",
#   "question": "上个月销售额最高的10个产品是什么?",
#   "answer": "上个月销售额最高的10个产品如下:\n1. iPhone 15 Pro - ¥1,280,000\n2. MacBook Pro - ¥980,000\n..."
# }
​
# 复杂查询
curl -X POST http://localhost:8080/api/data-query/query \
  -H "Content-Type: application/json" \
  -d '{"sessionId": "analyst1", "question": "对比最近3个月各品类的销售趋势"}'

7.3 查询优化与安全

@Component
public class QueryOptimizer {
​
    /**
     * 优化 SQL 查询
     */
    public String optimizeQuery(String naturalLanguage, String generatedSql) {
        // 1. 检查是否有必要索引
        if (generatedSql.contains("WHERE") && !generatedSql.contains("INDEX")) {
            // 提示添加索引
        }
​
        // 2. 限制返回行数
        if (!generatedSql.toUpperCase().contains("LIMIT")) {
            generatedSql += " LIMIT 1000";
        }
​
        // 3. 检查敏感字段
        if (containsSensitiveField(generatedSql)) {
            // 脱敏处理
        }
​
        return generatedSql;
    }
​
    private boolean containsSensitiveField(String sql) {
        String lowerSql = sql.toLowerCase();
        return lowerSql.contains("password") ||
               lowerSql.contains("phone") ||
               lowerSql.contains("id_card");
    }
}

八、企业级最佳实践

8.1 提示词工程(Prompt Engineering)

结构化提示词模板:

@Component
public class PromptTemplates {
​
    public static final String KNOWLEDGE_QA = """
        ## 角色
        你是企业知识库助手,专注于回答员工关于公司政策、流程、产品的问题。
​
        ## 规则
        1. 仅基于提供的上下文信息回答问题
        2. 如果上下文中没有相关信息,明确告知用户
        3. 不要编造或推测信息
        4. 回答要简洁、准确、专业
        5. 如果问题模糊,可以要求用户澄清
​
        ## 输出格式
        - 使用清晰的段落结构
        - 重要信息用加粗标注
        - 必要时使用列表或表格
​
        ## 上下文
        {context}
​
        ## 用户问题
        {question}
​
        ## 请回答
        """;
​
    public static final String SQL_GENERATION = """
        ## 任务
        将用户的自然语言问题转换为 SQL 查询语句。
​
        ## 数据库表结构
        {schema}
​
        ## 规则
        1. 只生成 SELECT 查询
        2. 使用标准 SQL 语法
        3. 添加适当的 LIMIT 限制
        4. 避免 SELECT *
        5. 对敏感字段进行脱敏
​
        ## 用户问题
        {question}
​
        ## 请生成 SQL
        """;
}

8.2 输出格式化

@Component
public class OutputParsers {
​
    /**
     * 解析 JSON 输出
     */
    public <T> T parseJson(String content, Class<T> clazz) {
        try {
            // 提取 JSON 部分
            String json = extractJson(content);
            return new ObjectMapper().readValue(json, clazz);
        } catch (Exception e) {
            throw new RuntimeException("解析输出失败", e);
        }
    }
​
    /**
     * 从内容中提取 JSON
     */
    private String extractJson(String content) {
        // 尝试找到 JSON 块
        int start = content.indexOf("{");
        int end = content.lastIndexOf("}");
​
        if (start != -1 && end != -1) {
            return content.substring(start, end + 1);
        }
​
        // 尝试 JSON 数组
        start = content.indexOf("[");
        end = content.lastIndexOf("]");
​
        if (start != -1 && end != -1) {
            return content.substring(start, end + 1);
        }
​
        throw new RuntimeException("未找到有效的 JSON");
    }
}

8.3 错误处理与重试

@Configuration
public class RetryConfig {
​
    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate template = new RetryTemplate();
​
        // 重试策略
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(3);
​
        // 退避策略
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        backOffPolicy.setInitialInterval(1000);
        backOffPolicy.setMultiplier(2);
        backOffPolicy.setMaxInterval(10000);
​
        template.setRetryPolicy(retryPolicy);
        template.setBackOffPolicy(backOffPolicy);
​
        return template;
    }
}
​
@Service
@Slf4j
@RequiredArgsConstructor
public class ResilientAiService {
​
    private final ChatLanguageModel chatModel;
    private final RetryTemplate retryTemplate;
​
    public String generateWithRetry(String prompt) {
        return retryTemplate.execute(context -> {
            log.info("调用 LLM,尝试次数: {}", context.getRetryCount() + 1);
            return chatModel.generate(prompt);
        }, context -> {
            log.error("LLM 调用失败,已重试 {} 次", context.getRetryCount());
            return "抱歉,AI 服务暂时不可用,请稍后再试。";
        });
    }
}

8.4 成本控制

@Component
@Slf4j
public class TokenManager {
​
    // Token 使用统计
    private final Map<String, AtomicInteger> dailyUsage = new ConcurrentHashMap<>();
​
    @Value("${app.token.daily-limit:100000}")
    private int dailyLimit;
​
    /**
     * 检查是否超出每日限额
     */
    public boolean checkLimit(String userId) {
        AtomicInteger usage = dailyUsage.computeIfAbsent(userId, k -> new AtomicInteger(0));
        return usage.get() < dailyLimit;
    }
​
    /**
     * 记录 Token 使用
     */
    public void recordUsage(String userId, int tokens) {
        AtomicInteger usage = dailyUsage.computeIfAbsent(userId, k -> new AtomicInteger(0));
        int total = usage.addAndGet(tokens);
        log.info("用户 {} 使用 {} tokens,今日累计: {}", userId, tokens, total);
    }
​
    /**
     * 优化 Prompt 以减少 Token 使用
     */
    public String optimizePrompt(String prompt) {
        // 1. 移除多余空白
        prompt = prompt.replaceAll("\\s+", " ").trim();
​
        // 2. 截断过长的上下文
        if (prompt.length() > 8000) {
            prompt = prompt.substring(0, 8000) + "\n...(内容已截断)";
        }
​
        return prompt;
    }
}

九、生产部署与监控

9.1 部署架构

┌─────────────────────────────────────────────────────────┐
│                      Nginx (负载均衡)                    │
└────────────────────────┬────────────────────────────────┘
                         │
        ┌────────────────┼────────────────┐
        ▼                ▼                ▼
   ┌─────────┐      ┌─────────┐      ┌─────────┐
   │ App-1   │      │ App-2   │      │ App-3   │
   │ (Pod)   │      │ (Pod)   │      │ (Pod)   │
   └────┬────┘      └────┬────┘      └────┬────┘
        │                │                │
        └────────────────┼────────────────┘
                         │
        ┌────────────────┼────────────────┐
        ▼                ▼                ▼
   ┌─────────┐      ┌─────────┐      ┌─────────┐
   │  Redis  │      │  MySQL  │      │  Milvus │
   │ (缓存)  │      │ (数据库)│      │ (向量库)│
   └─────────┘      └─────────┘      └─────────┘

9.2 Kubernetes 部署配置

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: langchain4j-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: langchain4j-app
  template:
    metadata:
      labels:
        app: langchain4j-app
    spec:
      containers:
        - name: app
          image: your-registry/langchain4j-app:latest
          ports:
            - containerPort: 8080
          env:
            - name: OPENAI_API_KEY
              valueFrom:
                secretKeyRef:
                  name: langchain4j-secrets
                  key: openai-api-key
            - name: SPRING_PROFILES_ACTIVE
              value: "prod"
          resources:
            requests:
              memory: "512Mi"
              cpu: "500m"
            limits:
              memory: "1Gi"
              cpu: "1000m"
          livenessProbe:
            httpGet:
              path: /actuator/health
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /actuator/health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 5

9.3 监控指标

@Component
@Slf4j
public class MetricsCollector {
​
    private final MeterRegistry meterRegistry;
​
    public MetricsCollector(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
​
    /**
     * 记录 LLM 调用指标
     */
    public void recordLlmCall(String model, long durationMs, int tokens, boolean success) {
        // 调用次数
        meterRegistry.counter("llm.calls.total",
            "model", model,
            "success", String.valueOf(success)
        ).increment();
​
        // 调用耗时
        meterRegistry.timer("llm.calls.duration",
            "model", model
        ).record(Duration.ofMillis(durationMs));
​
        // Token 使用
        meterRegistry.counter("llm.tokens.total",
            "model", model
        ).increment(tokens);
​
        log.debug("LLM 调用: model={}, duration={}ms, tokens={}, success={}",
            model, durationMs, tokens, success);
    }
​
    /**
     * 记录 RAG 检索指标
     */
    public void recordRagRetrieval(int retrievedDocs, long durationMs) {
        meterRegistry.timer("rag.retrieval.duration")
            .record(Duration.ofMillis(durationMs));
​
        meterRegistry.gauge("rag.retrieval.docs", retrievedDocs);
    }
}

9.4 性能优化建议

优化点 措施 效果
响应延迟 使用流式输出(Streaming) 首字响应时间降低 80%
成本控制 Prompt 缓存 + 压缩 Token 成本降低 30%
并发处理 异步调用 + 线程池 吞吐量提升 5 倍
检索速度 向量索引优化 检索延迟 < 100ms
可用性 多 LLM 供应商切换 可用性 99.9%

9.5 流式输出实现

@RestController
@RequestMapping("/api/stream")
@RequiredArgsConstructor
public class StreamController {
​
    private final ChatLanguageModel chatModel;
​
    /**
     * 流式对话接口
     */
    @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamChat(@RequestParam String message) {
        return Flux.create(sink -> {
            // 使用流式模型
            StreamingChatLanguageModel streamingModel = OpenAiStreamingChatModel.builder()
                .apiKey("your-api-key")
                .modelName("gpt-3.5-turbo")
                .build();
​
            streamingModel.chat(message, new StreamingChatResponseHandler() {
​
                @Override
                public void onPartialResponse(String partialResponse) {
                    sink.next(partialResponse);
                }
​
                @Override
                public void onCompleteResponse(ChatResponse response) {
                    sink.complete();
                }
​
                @Override
                public void onError(Throwable error) {
                    sink.error(error);
                }
            });
        });
    }
}

十、总结与展望

10.1 核心要点回顾

┌─────────────────────────────────────────────────────────┐
│                   LangChain4j 核心能力                   │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   1. 统一的 LLM 接口                                    │
│      └── 一套代码支持多种大模型                          │
│                                                         │
│   2. RAG(检索增强生成)                                 │
│      └── 让 AI 理解企业私有数据                         │
│                                                         │
│   3. 对话记忆                                           │
│      └── 多轮对话上下文管理                              │
│                                                         │
│   4. 工具调用(Function Calling)                       │
│      └── 让 AI 调用外部系统                             │
│                                                         │
│   5. AI Services(声明式接口)                          │
│      └── 像写 Feign 客户端一样写 AI 接口                │
│                                                         │
└─────────────────────────────────────────────────────────┘

10.2 适用场景

场景 推荐方案 复杂度
简单问答 Chat Model + Prompt
知识库问答 RAG ⭐⭐
智能客服 RAG + Memory + Tools ⭐⭐⭐
文档处理 Document Loaders + AI ⭐⭐⭐
数据分析 Text-to-SQL Agent ⭐⭐⭐⭐
复杂工作流 Multi-Agent ⭐⭐⭐⭐⭐

10.3 学习路线建议

第一阶段:基础入门
├── 理解 LLM 基本概念
├── LangChain4j Hello World
└── 调用 OpenAI API
​
第二阶段:核心功能
├── Prompt Engineering
├── 输出解析
└── 对话记忆
​
第三阶段:RAG 实战
├── 文档加载与解析
├── 向量数据库使用
└── 检索策略优化
​
第四阶段:Agent 开发
├── 工具调用
├── 多轮对话
└── 多 Agent 协作
​
第五阶段:企业级应用
├── 性能优化
├── 成本控制
└── 生产部署

10.4 未来展望

  1. 多模态支持:图像、音频、视频处理

  2. 更强大的 Agent:自主规划、多步推理

  3. 本地化部署:更多国产模型支持

  4. 低代码平台:可视化 AI 应用构建

  5. 标准化:AI 应用开发的最佳实践


参考资源


如果这篇文章对你有帮助,欢迎点赞、收藏、转发!有任何问题也欢迎在评论区留言讨论。

附录:常见问题解答

Q1: LangChain4j 和 Spring AI 有什么区别?

特性 LangChain4j Spring AI
成熟度 较成熟,社区活跃 较新,发展中
功能完整度 功能丰富 核心功能完整
Spring 集成 良好 原生支持
学习曲线 中等 较低
推荐场景 复杂 AI 应用 Spring 生态项目

Q2: 如何选择 LLM 提供商?

  • OpenAI:功能最强,成本较高,适合原型验证

  • Azure OpenAI:企业级,合规性好,适合生产环境

  • Ollama(本地):免费,数据不出境,适合敏感数据

  • 国产模型:中文优化,成本低,适合中文场景

Q3: 如何降低 LLM 调用成本?

  1. 使用更小的模型(GPT-3.5 vs GPT-4)

  2. 优化 Prompt,减少 Token 使用

  3. 实现缓存机制

  4. 批量处理请求

  5. 使用本地模型

Q4: RAG 的准确率如何提升?

  1. 优化文档分块策略

  2. 使用更好的嵌入模型

  3. 实现混合检索(向量 + 关键词)

  4. 添加重排序(Reranking)

  5. 持续优化 Prompt


全文完

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐