Spring AI Alibaba 深度解析(三):实战示例与最佳实践

一、完整项目搭建

1.1 项目初始化

使用 Spring Initializr

访问 https://start.spring.io 或使用 IDEA 创建 Spring Boot 项目。

配置项

  • Project: Maven
  • Language: Java
  • Spring Boot: 3.2.x
  • Java: 17
  • Packaging: Jar
Maven 依赖配置
xml 复制代码
<?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
         http://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>spring-ai-alibaba-demo</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>17</java.version>
        <spring-ai-alibaba.version>1.0.0-M2</spring-ai-alibaba.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot WebFlux (用于流式响应) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <!-- Spring AI Alibaba Starter -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter</artifactId>
            <version>${spring-ai-alibaba.version}</version>
        </dependency>

        <!-- PostgreSQL + pgvector (用于 RAG) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-pgvector-store</artifactId>
            <version>1.0.0-M3</version>
        </dependency>

        <!-- Validation -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
配置文件

application.yml:

yaml 复制代码
spring:
  application:
    name: spring-ai-alibaba-demo

  # DashScope 配置
  ai:
    dashscope:
      api-key: ${DASHSCOPE_API_KEY:your-api-key-here}
      chat:
        options:
          model: qwen-max
          temperature: 0.7
          top-p: 0.8
          max-tokens: 2000
      image:
        options:
          model: wanx-v1
      embedding:
        options:
          model: text-embedding-v2

  # 数据库配置(用于 RAG)
  datasource:
    url: jdbc:postgresql://localhost:5432/ai_db
    username: postgres
    password: postgres
    driver-class-name: org.postgresql.Driver

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect

# 服务器配置
server:
  port: 8080

# 日志配置
logging:
  level:
    com.alibaba.cloud.ai: DEBUG
    org.springframework.ai: DEBUG

二、实战示例 1:智能客服机器人

2.1 需求分析

构建一个智能客服系统,具备以下能力:

  • 多轮对话,保持上下文
  • 意图识别与分类
  • 接入外部系统(订单查询、退款处理等)
  • 知识库检索(FAQ、产品手册)

2.2 项目结构

复制代码
src/main/java/com/example/customerservice/
├── controller/
│   ├── ChatController.java
│   └── KnowledgeController.java
├── service/
│   ├── CustomerServiceChatService.java
│   ├── IntentRecognitionService.java
│   ├── OrderService.java
│   └── KnowledgeBaseService.java
├── model/
│   ├── ChatMessage.java
│   ├── ChatSession.java
│   └── Order.java
├── function/
│   ├── OrderQueryFunction.java
│   └── RefundFunction.java
└── config/
    ├── FunctionConfiguration.java
    └── VectorStoreConfiguration.java

2.3 核心代码实现

对话控制器
java 复制代码
@RestController
@RequestMapping("/api/chat")
@Slf4j
public class ChatController {

    @Autowired
    private CustomerServiceChatService chatService;

    /**
     * 同步对话接口
     */
    @PostMapping("/message")
    public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) {
        String response = chatService.chat(
            request.getSessionId(),
            request.getMessage()
        );

        return ResponseEntity.ok(new ChatResponse(response));
    }

    /**
     * 流式对话接口
     */
    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> chatStream(
            @RequestParam String sessionId,
            @RequestParam String message) {

        return chatService.chatStream(sessionId, message)
            .map(chunk -> ServerSentEvent.<String>builder()
                .data(chunk)
                .build());
    }

    /**
     * 获取对话历史
     */
    @GetMapping("/history/{sessionId}")
    public ResponseEntity<List<ChatMessage>> getHistory(
            @PathVariable String sessionId) {
        List<ChatMessage> history = chatService.getHistory(sessionId);
        return ResponseEntity.ok(history);
    }

    /**
     * 清除会话
     */
    @DeleteMapping("/session/{sessionId}")
    public ResponseEntity<Void> clearSession(@PathVariable String sessionId) {
        chatService.clearSession(sessionId);
        return ResponseEntity.ok().build();
    }
}

@Data
class ChatRequest {
    private String sessionId;
    private String message;
}

@Data
@AllArgsConstructor
class ChatResponse {
    private String message;
}
客服对话服务
java 复制代码
@Service
@Slf4j
public class CustomerServiceChatService {

    @Autowired
    private ChatClient chatClient;

    @Autowired
    private KnowledgeBaseService knowledgeBaseService;

    // 会话存储(生产环境建议使用 Redis)
    private final Map<String, ChatSession> sessions = new ConcurrentHashMap<>();

    /**
     * 同步对话
     */
    public String chat(String sessionId, String userMessage) {
        // 1. 获取或创建会话
        ChatSession session = getOrCreateSession(sessionId);

        // 2. 检索知识库
        List<String> relevantKnowledge = knowledgeBaseService
            .search(userMessage, 3);

        // 3. 构建系统提示词
        String systemPrompt = buildSystemPrompt(relevantKnowledge);

        // 4. 构建对话历史
        List<Message> messages = new ArrayList<>();
        messages.add(new SystemMessage(systemPrompt));
        messages.addAll(session.getHistory());
        messages.add(new UserMessage(userMessage));

        // 5. 调用 AI
        String response = chatClient.prompt()
            .messages(messages)
            .functions("queryOrder", "processRefund")  // 注册函数
            .call()
            .content();

        // 6. 保存对话历史
        session.addMessage(new ChatMessage("user", userMessage));
        session.addMessage(new ChatMessage("assistant", response));

        return response;
    }

    /**
     * 流式对话
     */
    public Flux<String> chatStream(String sessionId, String userMessage) {
        ChatSession session = getOrCreateSession(sessionId);

        List<String> relevantKnowledge = knowledgeBaseService
            .search(userMessage, 3);

        String systemPrompt = buildSystemPrompt(relevantKnowledge);

        List<Message> messages = new ArrayList<>();
        messages.add(new SystemMessage(systemPrompt));
        messages.addAll(session.getHistory());
        messages.add(new UserMessage(userMessage));

        // 保存用户消息
        session.addMessage(new ChatMessage("user", userMessage));

        // 用于累积完整响应
        StringBuilder fullResponse = new StringBuilder();

        return chatClient.prompt()
            .messages(messages)
            .functions("queryOrder", "processRefund")
            .stream()
            .content()
            .doOnNext(fullResponse::append)
            .doOnComplete(() -> {
                // 流结束后保存完整响应
                session.addMessage(new ChatMessage("assistant",
                    fullResponse.toString()));
            });
    }

    /**
     * 构建系统提示词
     */
    private String buildSystemPrompt(List<String> knowledge) {
        StringBuilder prompt = new StringBuilder();
        prompt.append("""
            你是一个专业的客服助手,负责回答用户的问题并协助处理业务。

            请遵循以下规则:
            1. 态度友好、耐心,使用礼貌用语
            2. 回答要准确、简洁,避免冗长
            3. 如果不确定,主动告知用户并建议转人工客服
            4. 优先使用知识库中的信息回答问题
            5. 涉及订单操作时,调用相应的函数获取实时信息

            """);

        if (!knowledge.isEmpty()) {
            prompt.append("知识库参考信息:\n\n");
            for (int i = 0; i < knowledge.size(); i++) {
                prompt.append(i + 1).append(". ").append(knowledge.get(i))
                    .append("\n\n");
            }
        }

        return prompt.toString();
    }

    /**
     * 获取或创建会话
     */
    private ChatSession getOrCreateSession(String sessionId) {
        return sessions.computeIfAbsent(sessionId, id -> {
            ChatSession session = new ChatSession();
            session.setSessionId(id);
            session.setCreatedAt(LocalDateTime.now());
            return session;
        });
    }

    public List<ChatMessage> getHistory(String sessionId) {
        ChatSession session = sessions.get(sessionId);
        return session != null ? session.getMessages() : List.of();
    }

    public void clearSession(String sessionId) {
        sessions.remove(sessionId);
    }
}
会话模型
java 复制代码
@Data
public class ChatSession {
    private String sessionId;
    private List<ChatMessage> messages = new ArrayList<>();
    private LocalDateTime createdAt;
    private LocalDateTime lastAccessTime;

    public void addMessage(ChatMessage message) {
        this.messages.add(message);
        this.lastAccessTime = LocalDateTime.now();
    }

    public List<Message> getHistory() {
        return messages.stream()
            .map(msg -> {
                if ("user".equals(msg.getRole())) {
                    return new UserMessage(msg.getContent());
                } else {
                    return new AssistantMessage(msg.getContent());
                }
            })
            .collect(Collectors.toList());
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatMessage {
    private String role;      // user, assistant, system
    private String content;
    private LocalDateTime timestamp = LocalDateTime.now();

    public ChatMessage(String role, String content) {
        this.role = role;
        this.content = content;
    }
}
Function 定义
java 复制代码
@Configuration
public class FunctionConfiguration {

    @Autowired
    private OrderService orderService;

    /**
     * 订单查询函数
     */
    @Bean
    @Description("查询用户的订单信息,包括订单状态、物流信息等")
    public Function<OrderQueryRequest, OrderQueryResponse> queryOrder() {
        return request -> {
            log.info("Querying order: {}", request.orderId());

            Order order = orderService.getOrderById(request.orderId());

            if (order == null) {
                return new OrderQueryResponse(
                    false,
                    "未找到订单信息",
                    null
                );
            }

            return new OrderQueryResponse(
                true,
                "查询成功",
                order
            );
        };
    }

    /**
     * 退款处理函数
     */
    @Bean
    @Description("处理用户的退款申请")
    public Function<RefundRequest, RefundResponse> processRefund() {
        return request -> {
            log.info("Processing refund for order: {}", request.orderId());

            // 验证订单
            Order order = orderService.getOrderById(request.orderId());
            if (order == null) {
                return new RefundResponse(
                    false,
                    "订单不存在",
                    null
                );
            }

            // 检查是否符合退款条件
            if (!order.canRefund()) {
                return new RefundResponse(
                    false,
                    "该订单不符合退款条件",
                    null
                );
            }

            // 创建退款单
            String refundId = orderService.createRefund(
                request.orderId(),
                request.reason()
            );

            return new RefundResponse(
                true,
                "退款申请已提交,退款单号:" + refundId,
                refundId
            );
        };
    }
}

// 请求/响应模型
record OrderQueryRequest(
    @JsonProperty(required = true)
    @JsonPropertyDescription("订单ID")
    String orderId
) {}

record OrderQueryResponse(
    boolean success,
    String message,
    Order order
) {}

record RefundRequest(
    @JsonProperty(required = true)
    @JsonPropertyDescription("订单ID")
    String orderId,

    @JsonProperty(required = true)
    @JsonPropertyDescription("退款原因")
    String reason
) {}

record RefundResponse(
    boolean success,
    String message,
    String refundId
) {}
订单服务(模拟)
java 复制代码
@Service
public class OrderService {

    // 模拟订单数据库
    private final Map<String, Order> orders = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        // 初始化测试数据
        Order order1 = new Order();
        order1.setOrderId("ORD001");
        order1.setUserId("user123");
        order1.setProductName("iPhone 15 Pro");
        order1.setPrice(new BigDecimal("7999.00"));
        order1.setStatus("已发货");
        order1.setLogistics("顺丰快递,运单号:SF1234567890");
        order1.setCreateTime(LocalDateTime.now().minusDays(2));

        orders.put(order1.getOrderId(), order1);
    }

    public Order getOrderById(String orderId) {
        return orders.get(orderId);
    }

    public String createRefund(String orderId, String reason) {
        // 生成退款单号
        String refundId = "REF" + System.currentTimeMillis();

        // 更新订单状态
        Order order = orders.get(orderId);
        if (order != null) {
            order.setStatus("退款中");
        }

        return refundId;
    }
}

@Data
public class Order {
    private String orderId;
    private String userId;
    private String productName;
    private BigDecimal price;
    private String status;
    private String logistics;
    private LocalDateTime createTime;

    public boolean canRefund() {
        // 7天内且未完成的订单可以退款
        return ChronoUnit.DAYS.between(createTime, LocalDateTime.now()) <= 7
               && !"已完成".equals(status);
    }
}

2.4 知识库服务(RAG)

java 复制代码
@Service
@Slf4j
public class KnowledgeBaseService {

    @Autowired
    private VectorStore vectorStore;

    @Autowired
    private EmbeddingModel embeddingModel;

    /**
     * 添加知识到知识库
     */
    public void addKnowledge(String content, Map<String, Object> metadata) {
        Document document = new Document(content, metadata);
        vectorStore.add(List.of(document));
        log.info("Added knowledge to vector store");
    }

    /**
     * 批量添加知识
     */
    public void addKnowledgeBatch(List<String> contents) {
        List<Document> documents = contents.stream()
            .map(content -> new Document(
                UUID.randomUUID().toString(),
                content,
                Map.of("source", "manual", "timestamp", System.currentTimeMillis())
            ))
            .toList();

        vectorStore.add(documents);
        log.info("Added {} documents to vector store", documents.size());
    }

    /**
     * 搜索相关知识
     */
    public List<String> search(String query, int topK) {
        SearchRequest request = SearchRequest.builder()
            .query(query)
            .topK(topK)
            .similarityThreshold(0.7)
            .build();

        List<Document> results = vectorStore.similaritySearch(request);

        return results.stream()
            .map(Document::getContent)
            .toList();
    }

    /**
     * 从文件加载知识库
     */
    public void loadFromFile(String filePath) throws IOException {
        List<String> lines = Files.readAllLines(Paths.get(filePath));

        // 按段落分割
        List<String> paragraphs = new ArrayList<>();
        StringBuilder current = new StringBuilder();

        for (String line : lines) {
            if (line.trim().isEmpty()) {
                if (current.length() > 0) {
                    paragraphs.add(current.toString());
                    current = new StringBuilder();
                }
            } else {
                current.append(line).append("\n");
            }
        }

        if (current.length() > 0) {
            paragraphs.add(current.toString());
        }

        addKnowledgeBatch(paragraphs);
    }
}
知识库控制器
java 复制代码
@RestController
@RequestMapping("/api/knowledge")
public class KnowledgeController {

    @Autowired
    private KnowledgeBaseService knowledgeService;

    @PostMapping("/add")
    public ResponseEntity<String> addKnowledge(@RequestBody KnowledgeRequest request) {
        knowledgeService.addKnowledge(request.getContent(), request.getMetadata());
        return ResponseEntity.ok("Knowledge added successfully");
    }

    @PostMapping("/batch")
    public ResponseEntity<String> addKnowledgeBatch(
            @RequestBody List<String> contents) {
        knowledgeService.addKnowledgeBatch(contents);
        return ResponseEntity.ok("Batch knowledge added successfully");
    }

    @GetMapping("/search")
    public ResponseEntity<List<String>> search(
            @RequestParam String query,
            @RequestParam(defaultValue = "5") int topK) {
        List<String> results = knowledgeService.search(query, topK);
        return ResponseEntity.ok(results);
    }

    @PostMapping("/load-file")
    public ResponseEntity<String> loadFromFile(@RequestParam String filePath) {
        try {
            knowledgeService.loadFromFile(filePath);
            return ResponseEntity.ok("File loaded successfully");
        } catch (IOException e) {
            return ResponseEntity.status(500)
                .body("Failed to load file: " + e.getMessage());
        }
    }
}

@Data
class KnowledgeRequest {
    private String content;
    private Map<String, Object> metadata;
}

2.5 测试与验证

启动应用
bash 复制代码
mvn spring-boot:run
API 测试

1. 简单对话

bash 复制代码
curl -X POST http://localhost:8080/api/chat/message \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "session123",
    "message": "你好,我想了解一下退货政策"
  }'

2. 订单查询(触发 Function Calling)

bash 复制代码
curl -X POST http://localhost:8080/api/chat/message \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "session123",
    "message": "帮我查询订单 ORD001 的物流信息"
  }'

3. 流式对话

bash 复制代码
curl -N http://localhost:8080/api/chat/stream?sessionId=session123&message=介绍一下你们的产品

4. 添加知识库

bash 复制代码
curl -X POST http://localhost:8080/api/knowledge/batch \
  -H "Content-Type: application/json" \
  -d '[
    "我们的退货政策:购买后7天内,商品未使用且包装完好,可以申请退货。",
    "配送说明:工作日下单,当天发货;节假日下单,次日发货。支持顺丰、圆通等快递。",
    "售后服务:产品享有1年质保,质保期内免费维修。"
  ]'

三、实战示例 2:文档智能问答系统

3.1 需求描述

构建一个基于企业文档的智能问答系统,支持:

  • PDF、Word、Markdown 等多种格式文档导入
  • 文档内容向量化存储
  • 基于文档内容的精准问答
  • 支持引用来源追溯

3.2 核心实现

文档处理服务
java 复制代码
@Service
@Slf4j
public class DocumentProcessingService {

    @Autowired
    private VectorStore vectorStore;

    @Autowired
    private TextSplitter textSplitter;

    /**
     * 处理 PDF 文档
     */
    public void processPdfDocument(MultipartFile file) throws IOException {
        // 1. 读取 PDF
        PDDocument document = PDDocument.load(file.getInputStream());
        PDFTextStripper stripper = new PDFTextStripper();
        String content = stripper.getText(document);
        document.close();

        // 2. 创建文档对象
        Map<String, Object> metadata = Map.of(
            "filename", file.getOriginalFilename(),
            "type", "pdf",
            "size", file.getSize(),
            "uploadTime", LocalDateTime.now().toString()
        );

        Document doc = new Document(content, metadata);

        // 3. 文档分割
        List<Document> chunks = textSplitter.split(List.of(doc));

        // 4. 存储到向量库
        vectorStore.add(chunks);

        log.info("Processed PDF document: {}, created {} chunks",
            file.getOriginalFilename(), chunks.size());
    }

    /**
     * 处理 Markdown 文档
     */
    public void processMarkdownDocument(MultipartFile file) throws IOException {
        String content = new String(file.getBytes(), StandardCharsets.UTF_8);

        Map<String, Object> metadata = Map.of(
            "filename", file.getOriginalFilename(),
            "type", "markdown",
            "uploadTime", LocalDateTime.now().toString()
        );

        Document doc = new Document(content, metadata);
        List<Document> chunks = textSplitter.split(List.of(doc));
        vectorStore.add(chunks);

        log.info("Processed Markdown document: {}, created {} chunks",
            file.getOriginalFilename(), chunks.size());
    }

    /**
     * 处理纯文本
     */
    public void processTextDocument(String content, String filename) {
        Map<String, Object> metadata = Map.of(
            "filename", filename,
            "type", "text",
            "uploadTime", LocalDateTime.now().toString()
        );

        Document doc = new Document(content, metadata);
        List<Document> chunks = textSplitter.split(List.of(doc));
        vectorStore.add(chunks);

        log.info("Processed text document: {}, created {} chunks",
            filename, chunks.size());
    }
}
问答服务
java 复制代码
@Service
public class DocumentQAService {

    @Autowired
    private ChatClient chatClient;

    @Autowired
    private VectorStore vectorStore;

    /**
     * 基于文档的问答
     */
    public QAResponse answer(String question) {
        // 1. 检索相关文档片段
        SearchRequest searchRequest = SearchRequest.builder()
            .query(question)
            .topK(5)
            .similarityThreshold(0.75)
            .build();

        List<Document> relevantDocs = vectorStore.similaritySearch(searchRequest);

        if (relevantDocs.isEmpty()) {
            return new QAResponse(
                "抱歉,我在文档中没有找到相关信息来回答您的问题。",
                List.of(),
                0.0
            );
        }

        // 2. 构建上下文
        String context = relevantDocs.stream()
            .map(doc -> doc.getContent())
            .collect(Collectors.joining("\n\n---\n\n"));

        // 3. 构建提示词
        String prompt = String.format("""
            请基于以下文档内容回答用户的问题。

            文档内容:
            %s

            用户问题:%s

            回答要求:
            1. 仅基于提供的文档内容回答
            2. 如果文档中没有相关信息,请明确说明
            3. 回答要准确、简洁、专业
            4. 可以适当重组和总结文档内容
            """, context, question);

        // 4. 调用 LLM 生成回答
        String answer = chatClient.prompt()
            .user(prompt)
            .call()
            .content();

        // 5. 提取引用来源
        List<Source> sources = relevantDocs.stream()
            .map(doc -> new Source(
                (String) doc.getMetadata().get("filename"),
                doc.getContent().substring(0, Math.min(100, doc.getContent().length())) + "..."
            ))
            .toList();

        // 6. 计算置信度(基于检索的平均相似度)
        double confidence = calculateConfidence(relevantDocs);

        return new QAResponse(answer, sources, confidence);
    }

    private double calculateConfidence(List<Document> docs) {
        // 简化的置信度计算
        if (docs.isEmpty()) {
            return 0.0;
        }
        // 实际应该基于相似度分数计算
        return 0.85;
    }
}

@Data
@AllArgsConstructor
class QAResponse {
    private String answer;
    private List<Source> sources;
    private double confidence;
}

@Data
@AllArgsConstructor
class Source {
    private String filename;
    private String snippet;
}
API 控制器
java 复制代码
@RestController
@RequestMapping("/api/document-qa")
public class DocumentQAController {

    @Autowired
    private DocumentProcessingService documentService;

    @Autowired
    private DocumentQAService qaService;

    @PostMapping("/upload/pdf")
    public ResponseEntity<String> uploadPdf(@RequestParam("file") MultipartFile file) {
        try {
            documentService.processPdfDocument(file);
            return ResponseEntity.ok("PDF document uploaded and processed successfully");
        } catch (Exception e) {
            return ResponseEntity.status(500)
                .body("Failed to process PDF: " + e.getMessage());
        }
    }

    @PostMapping("/upload/markdown")
    public ResponseEntity<String> uploadMarkdown(@RequestParam("file") MultipartFile file) {
        try {
            documentService.processMarkdownDocument(file);
            return ResponseEntity.ok("Markdown document uploaded and processed successfully");
        } catch (Exception e) {
            return ResponseEntity.status(500)
                .body("Failed to process Markdown: " + e.getMessage());
        }
    }

    @PostMapping("/upload/text")
    public ResponseEntity<String> uploadText(@RequestBody TextUploadRequest request) {
        documentService.processTextDocument(request.getContent(), request.getFilename());
        return ResponseEntity.ok("Text document processed successfully");
    }

    @PostMapping("/ask")
    public ResponseEntity<QAResponse> ask(@RequestBody QuestionRequest request) {
        QAResponse response = qaService.answer(request.getQuestion());
        return ResponseEntity.ok(response);
    }
}

@Data
class TextUploadRequest {
    private String content;
    private String filename;
}

@Data
class QuestionRequest {
    private String question;
}

四、实战示例 3:内容生成助手

4.1 需求描述

构建一个多功能的内容生成助手:

  • 商品文案生成
  • 营销邮件撰写
  • 社交媒体内容创作
  • 结构化数据提取

4.2 核心实现

java 复制代码
@Service
public class ContentGenerationService {

    @Autowired
    private ChatClient chatClient;

    /**
     * 生成商品文案
     */
    public ProductCopy generateProductCopy(ProductInfo product) {
        String prompt = String.format("""
            请为以下商品撰写吸引人的营销文案:

            商品名称:%s
            商品类别:%s
            主要特点:%s
            价格:%s
            目标用户:%s

            请生成:
            1. 标题(20字以内,吸引眼球)
            2. 简短描述(50字以内)
            3. 详细描述(200字左右,突出卖点)
            4. 5个营销标签

            输出格式为 JSON。
            """,
            product.getName(),
            product.getCategory(),
            product.getFeatures(),
            product.getPrice(),
            product.getTargetAudience()
        );

        BeanOutputParser<ProductCopy> parser =
            new BeanOutputParser<>(ProductCopy.class);

        String response = chatClient.prompt()
            .user(prompt + "\n\n" + parser.getFormat())
            .call()
            .content();

        return parser.parse(response);
    }

    /**
     * 生成营销邮件
     */
    public String generateMarketingEmail(EmailRequest request) {
        String prompt = String.format("""
            请撰写一封营销邮件:

            主题:%s
            目标受众:%s
            关键信息:%s
            Call to Action:%s

            要求:
            1. 邮件要专业且友好
            2. 突出价值主张
            3. 包含清晰的行动号召
            4. 长度控制在300字左右
            """,
            request.getSubject(),
            request.getAudience(),
            request.getKeyPoints(),
            request.getCallToAction()
        );

        return chatClient.prompt()
            .user(prompt)
            .call()
            .content();
    }

    /**
     * 生成社交媒体内容
     */
    public SocialMediaContent generateSocialContent(
            String topic,
            String platform,
            String tone) {

        String prompt = String.format("""
            为 %s 平台创作内容:

            主题:%s
            风格:%s

            要求:
            1. 符合平台特点和字数限制
            2. 包含相关话题标签
            3. 语言风格符合要求
            4. 具有传播性和互动性
            """,
            platform,
            topic,
            tone
        );

        BeanOutputParser<SocialMediaContent> parser =
            new BeanOutputParser<>(SocialMediaContent.class);

        String response = chatClient.prompt()
            .user(prompt + "\n\n" + parser.getFormat())
            .call()
            .content();

        return parser.parse(response);
    }

    /**
     * 从非结构化文本提取信息
     */
    public PersonInfo extractPersonInfo(String text) {
        String prompt = String.format("""
            从以下文本中提取人物信息:

            %s

            请提取:
            - 姓名
            - 年龄
            - 职业
            - 联系方式(邮箱、电话)
            - 地址

            输出格式为 JSON。
            """,
            text
        );

        BeanOutputParser<PersonInfo> parser =
            new BeanOutputParser<>(PersonInfo.class);

        String response = chatClient.prompt()
            .user(prompt + "\n\n" + parser.getFormat())
            .call()
            .content();

        return parser.parse(response);
    }
}

// 数据模型
@Data
class ProductInfo {
    private String name;
    private String category;
    private String features;
    private String price;
    private String targetAudience;
}

@Data
class ProductCopy {
    private String title;
    private String shortDescription;
    private String detailedDescription;
    private List<String> tags;
}

@Data
class EmailRequest {
    private String subject;
    private String audience;
    private String keyPoints;
    private String callToAction;
}

@Data
class SocialMediaContent {
    private String content;
    private List<String> hashtags;
    private String imagePrompt;
}

@Data
class PersonInfo {
    private String name;
    private Integer age;
    private String occupation;
    private String email;
    private String phone;
    private String address;
}

五、最佳实践

5.1 提示词工程

1. 使用清晰的指令
java 复制代码
// ❌ 不好的提示词
String prompt = "写个文案";

// ✅ 好的提示词
String prompt = """
    请为智能手表撰写一份产品文案。

    要求:
    1. 突出健康监测功能
    2. 目标用户是年轻白领
    3. 字数控制在200字以内
    4. 语言风格:专业且不失活力
    """;
2. 提供示例(Few-shot Learning)
java 复制代码
String prompt = """
    将以下用户反馈分类为:功能建议、Bug反馈、使用咨询。

    示例:
    输入:应用闪退了
    输出:Bug反馈

    输入:能不能加个夜间模式?
    输出:功能建议

    输入:怎么修改密码?
    输出:使用咨询

    现在请分类:%s
    """.formatted(userFeedback);
3. 角色设定
java 复制代码
String systemPrompt = """
    你是一位资深的技术文档撰写专家,拥有10年以上的技术写作经验。
    你擅长将复杂的技术概念用通俗易懂的语言解释清楚。
    你的文档风格严谨、准确,同时注重可读性。
    """;

5.2 错误处理

java 复制代码
@Service
public class RobustChatService {

    @Autowired
    private ChatClient chatClient;

    @Retryable(
        value = {HttpClientErrorException.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000, multiplier = 2)
    )
    public String chatWithRetry(String message) {
        try {
            return chatClient.prompt()
                .user(message)
                .call()
                .content();

        } catch (HttpClientErrorException.TooManyRequests e) {
            // 速率限制
            log.warn("Rate limit exceeded, will retry");
            throw e;

        } catch (HttpClientErrorException.BadRequest e) {
            // 请求参数错误,不重试
            log.error("Bad request: {}", e.getMessage());
            return "抱歉,您的请求格式有误,请重试。";

        } catch (Exception e) {
            // 其他错误
            log.error("Unexpected error: {}", e.getMessage(), e);
            return "抱歉,服务暂时不可用,请稍后重试。";
        }
    }

    @CircuitBreaker(name = "chatService", fallbackMethod = "chatFallback")
    public String chatWithCircuitBreaker(String message) {
        return chatClient.prompt()
            .user(message)
            .call()
            .content();
    }

    public String chatFallback(String message, Exception e) {
        log.error("Circuit breaker activated: {}", e.getMessage());
        return "当前服务繁忙,请稍后重试。";
    }
}

5.3 Token 管理

java 复制代码
@Component
public class TokenCounter {

    private final Tokenizer tokenizer;

    /**
     * 估算 Token 数量
     */
    public int countTokens(String text) {
        return tokenizer.encode(text).size();
    }

    /**
     * 截断文本以符合 Token 限制
     */
    public String truncate(String text, int maxTokens) {
        List<Integer> tokens = tokenizer.encode(text);

        if (tokens.size() <= maxTokens) {
            return text;
        }

        List<Integer> truncated = tokens.subList(0, maxTokens);
        return tokenizer.decode(truncated);
    }

    /**
     * 智能截断(保留完整句子)
     */
    public String smartTruncate(String text, int maxTokens) {
        if (countTokens(text) <= maxTokens) {
            return text;
        }

        String[] sentences = text.split("[。!?]");
        StringBuilder result = new StringBuilder();
        int currentTokens = 0;

        for (String sentence : sentences) {
            int sentenceTokens = countTokens(sentence);

            if (currentTokens + sentenceTokens > maxTokens) {
                break;
            }

            result.append(sentence).append("。");
            currentTokens += sentenceTokens;
        }

        return result.toString();
    }
}

5.4 成本优化

java 复制代码
@Service
public class CostOptimizedChatService {

    @Autowired
    private ChatClient chatClient;

    @Autowired
    @Qualifier("cachedChatModel")
    private ChatModel cachedModel;

    /**
     * 使用缓存减少重复调用
     */
    @Cacheable(value = "chat-responses", key = "#message")
    public String chatWithCache(String message) {
        return chatClient.prompt()
            .user(message)
            .call()
            .content();
    }

    /**
     * 根据任务复杂度选择模型
     */
    public String chatWithModelSelection(String message, boolean isComplex) {
        String model = isComplex ? "qwen-max" : "qwen-turbo";

        return chatClient.prompt()
            .user(message)
            .options(ChatOptions.builder()
                .model(model)
                .build())
            .call()
            .content();
    }

    /**
     * 批处理减少请求次数
     */
    public List<String> batchProcess(List<String> messages) {
        // 合并为一个请求
        String combined = String.join("\n\n---\n\n", messages);

        String response = chatClient.prompt()
            .user("请分别回答以下问题:\n\n" + combined)
            .call()
            .content();

        // 分割响应
        return Arrays.asList(response.split("---"));
    }
}

5.5 安全实践

java 复制代码
@Component
public class SecurityFilter {

    private final Set<String> sensitiveWords = Set.of(
        "密码", "身份证", "银行卡"
    );

    /**
     * 敏感词过滤
     */
    public String filterSensitiveInfo(String text) {
        String filtered = text;

        for (String word : sensitiveWords) {
            filtered = filtered.replaceAll(
                word + "[::]?\\s*\\S+",
                word + ": [已隐藏]"
            );
        }

        return filtered;
    }

    /**
     * 输入验证
     */
    public void validateInput(String input) {
        if (input == null || input.trim().isEmpty()) {
            throw new IllegalArgumentException("输入不能为空");
        }

        if (input.length() > 10000) {
            throw new IllegalArgumentException("输入内容过长");
        }

        // 检查是否包含恶意内容
        if (containsMaliciousContent(input)) {
            throw new SecurityException("检测到不当内容");
        }
    }

    private boolean containsMaliciousContent(String input) {
        // 实现恶意内容检测逻辑
        return false;
    }
}

5.6 监控与日志

java 复制代码
@Aspect
@Component
public class ChatMonitoringAspect {

    @Autowired
    private MeterRegistry meterRegistry;

    @Around("@annotation(com.example.Monitored)")
    public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();

        Timer.Sample sample = Timer.start(meterRegistry);

        try {
            Object result = joinPoint.proceed();

            sample.stop(Timer.builder("chat.calls")
                .tag("method", methodName)
                .tag("status", "success")
                .register(meterRegistry));

            return result;

        } catch (Exception e) {
            sample.stop(Timer.builder("chat.calls")
                .tag("method", methodName)
                .tag("status", "error")
                .register(meterRegistry));

            Counter.builder("chat.errors")
                .tag("method", methodName)
                .tag("exception", e.getClass().getSimpleName())
                .register(meterRegistry)
                .increment();

            throw e;
        }
    }
}

六、生产部署建议

6.1 配置管理

使用配置中心(如 Nacos)集中管理配置:

yaml 复制代码
# Nacos 配置
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yaml
        namespace: production
        group: AI_SERVICE

6.2 数据库优化

PostgreSQL + pgvector 优化:

sql 复制代码
-- 创建索引加速向量搜索
CREATE INDEX ON vector_store USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

-- 定期 VACUUM
VACUUM ANALYZE vector_store;

-- 分区表(大数据量场景)
CREATE TABLE vector_store_2024_01 PARTITION OF vector_store
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');

6.3 高可用部署

yaml 复制代码
# 负载均衡
spring:
  cloud:
    loadbalancer:
      enabled: true

# 健康检查
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  health:
    circuitbreakers:
      enabled: true
相关推荐
梁同学与Android2 小时前
Android ---【经验篇】ArrayList vs CopyOnWriteArrayList 核心区别,怎么选择?
android·java·开发语言
北邮刘老师2 小时前
【智能体互联协议解析】需要“智能体名字系统”(ANS)吗?
网络·人工智能·大模型·智能体·智能体互联网
ss2732 小时前
从零实现线程池:自定义线程池的工作线程设计与实现
java·开发语言·jvm
苗壮.2 小时前
CommandLineRunner 是什么?
java
石工记2 小时前
windows 10直接安装多个JDK
java·开发语言
菜鸟233号2 小时前
力扣669 修剪二叉搜索树 java实现
java·数据结构·算法·leetcode
梁辰兴3 小时前
AI解码千年甲骨文,指尖触碰的文明觉醒!
人工智能·ai·ai+·文明·甲骨文·ai赋能·梁辰兴
阿里云大数据AI技术3 小时前
# Hologres Dynamic Table:高效增量刷新,构建实时统一数仓的核心利器
人工智能·数据分析
健康平安的活着3 小时前
springboot+sse的实现案例
java·spring boot·后端