一、完整项目搭建
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