Browse Source

代码

master
zhoukaihong@idcos.com 10 months ago
commit
fff93846e6
  1. 99
      pom.xml
  2. 55
      src/main/java/com/xs/ai/SpringAiDemoApplication.java
  3. 30
      src/main/java/com/xs/ai/controller/BookingController.java
  4. 91
      src/main/java/com/xs/ai/controller/OpenAiController.java
  5. 98
      src/main/java/com/xs/ai/data/Booking.java
  6. 7
      src/main/java/com/xs/ai/data/BookingClass.java
  7. 28
      src/main/java/com/xs/ai/data/BookingData.java
  8. 7
      src/main/java/com/xs/ai/data/BookingStatus.java
  9. 35
      src/main/java/com/xs/ai/data/Customer.java
  10. 72
      src/main/java/com/xs/ai/services/BookingTools.java
  11. 84
      src/main/java/com/xs/ai/services/CustomerSupportAssistant.java
  12. 101
      src/main/java/com/xs/ai/services/FlightBookingService.java
  13. 20
      src/main/java/com/xs/ai/services/LoggingAdvisor.java
  14. 4
      src/main/resources/application.properties
  15. 33
      src/main/resources/application.yml
  16. 13
      src/main/resources/rag/terms-of-service.txt
  17. 13
      src/test/java/com/xs/ai/SpringAiDemoApplicationTests.java

99
pom.xml

@ -0,0 +1,99 @@
<?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.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jzx</groupId>
<artifactId>tuling-flight-booking</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>tuling-flight-booking</name>
<description>stuling-flight-booking</description>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M3</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--
通义
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0-M3.3</version>
</dependency>-->
<!--
openAI
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>-->
<!--
千帆: 只能用v1 .v2不适配
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-qianfan-spring-boot-starter</artifactId>
</dependency>-->
<!--ollama-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>

55
src/main/java/com/xs/ai/SpringAiDemoApplication.java

@ -0,0 +1,55 @@
package com.xs.ai;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
/**
* @author xushu
* @version 1.0.0
* @description
*/
@SpringBootApplication
public class SpringAiDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAiDemoApplication.class, args);
}
// In the real world, ingesting documents would often happen separately, on a CI
// server or similar.
@Bean
CommandLineRunner ingestTermOfServiceToVectorStore(EmbeddingModel embeddingModel, VectorStore vectorStore,
@Value("classpath:rag/terms-of-service.txt") Resource termsOfServiceDocs) {
return args -> {
// Ingest the document into the vector store
vectorStore.write( // 3.写入
new TokenTextSplitter().transform( // 2.转换
new TextReader(termsOfServiceDocs).read()) // 1.读取
);
};
}
@Bean
public ChatMemory chatMemory() {
return new InMemoryChatMemory();
}
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return new SimpleVectorStore(embeddingModel);
}
}

30
src/main/java/com/xs/ai/controller/BookingController.java

@ -0,0 +1,30 @@
package com.xs.ai.controller;
import com.xs.ai.services.BookingTools;
import com.xs.ai.services.BookingTools.BookingDetails;
import com.xs.ai.services.FlightBookingService;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@CrossOrigin
public class BookingController {
private final FlightBookingService flightBookingService;
public BookingController(FlightBookingService flightBookingService) {
this.flightBookingService = flightBookingService;
}
@CrossOrigin
@GetMapping(value = "/booking/list")
public List<BookingDetails> getBookings() {
return flightBookingService.getBookings();
}
}

91
src/main/java/com/xs/ai/controller/OpenAiController.java

@ -0,0 +1,91 @@
package com.xs.ai.controller;
import com.xs.ai.services.LoggingAdvisor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.time.LocalDate;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;
/**
* @author xushu
* @version 1.0
* @description:
*/
@RestController
@CrossOrigin
public class OpenAiController {
private final ChatClient chatClient;
public OpenAiController(ChatClient.Builder chatClientBuilder, VectorStore vectorStore, ChatMemory chatMemory) {
this.chatClient = chatClientBuilder
.defaultSystem("""
您是Tuling航空公司的客户聊天支持代理请以友好乐于助人且愉快的方式来回复
您正在通过在线聊天系统与客户互动
在提供有关预订或取消预订的信息之前您必须始终
从用户处获取以下信息预订号客户姓名
在询问用户之前请检查消息历史记录以获取此信息
在更改或退订之前请先获取预订信息并且告知条款待用户回复确定之后才进行更改或退订的function-call
请讲中文
今天的日期是 {current_date}.
""")
.defaultAdvisors(
new PromptChatMemoryAdvisor(chatMemory),
new QuestionAnswerAdvisor(vectorStore, SearchRequest.query("预定航班")), // RAG
new LoggingAdvisor())
.defaultFunctions("getBookingDetails", "changeBooking", "cancelBooking") // FUNCTION CALLING
.build();
}
@CrossOrigin
@GetMapping(value = "/ai/generateStreamAsString", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> generateStreamAsString(@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {
//Prompt prompt = new Prompt(new UserMessage(message));
//return chatClient.stream(prompt);
Flux<String> content = chatClient.prompt()
.system(s -> s.param("current_date", LocalDate.now().toString()))
//.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId).param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
.advisors(a -> a.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
.user(message)
.stream()
.content();
return content
.concatWith(Flux.just("[complete]"));
}
@Autowired
ChatClient.Builder chatClientBuilder;
@CrossOrigin
@GetMapping(value = "/ai/chat")
public String chat(@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {
return chatClient.prompt()
.system(s -> s.param("current_date", LocalDate.now().toString()))
.user(message)
.call()
.content();
}
}

98
src/main/java/com/xs/ai/data/Booking.java

@ -0,0 +1,98 @@
package com.xs.ai.data;
import java.time.LocalDate;
public class Booking {
private String bookingNumber;
private LocalDate date;
private LocalDate bookingTo;
private Customer customer;
private String from;
private String to;
private BookingStatus bookingStatus;
private BookingClass bookingClass;
public Booking(String bookingNumber, LocalDate date, Customer customer, BookingStatus bookingStatus, String from,
String to, BookingClass bookingClass) {
this.bookingNumber = bookingNumber;
this.date = date;
this.customer = customer;
this.bookingStatus = bookingStatus;
this.from = from;
this.to = to;
this.bookingClass = bookingClass;
}
public String getBookingNumber() {
return bookingNumber;
}
public void setBookingNumber(String bookingNumber) {
this.bookingNumber = bookingNumber;
}
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
public LocalDate getBookingTo() {
return bookingTo;
}
public void setBookingTo(LocalDate bookingTo) {
this.bookingTo = bookingTo;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public BookingStatus getBookingStatus() {
return bookingStatus;
}
public void setBookingStatus(BookingStatus bookingStatus) {
this.bookingStatus = bookingStatus;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public BookingClass getBookingClass() {
return bookingClass;
}
public void setBookingClass(BookingClass bookingClass) {
this.bookingClass = bookingClass;
}
}

7
src/main/java/com/xs/ai/data/BookingClass.java

@ -0,0 +1,7 @@
package com.xs.ai.data;
public enum BookingClass {
ECONOMY, PREMIUM_ECONOMY, BUSINESS
}

28
src/main/java/com/xs/ai/data/BookingData.java

@ -0,0 +1,28 @@
package com.xs.ai.data;
import java.util.ArrayList;
import java.util.List;
public class BookingData {
private List<Customer> customers = new ArrayList<>();
private List<Booking> bookings = new ArrayList<>();
public List<Customer> getCustomers() {
return customers;
}
public void setCustomers(List<Customer> customers) {
this.customers = customers;
}
public List<Booking> getBookings() {
return bookings;
}
public void setBookings(List<Booking> bookings) {
this.bookings = bookings;
}
}

7
src/main/java/com/xs/ai/data/BookingStatus.java

@ -0,0 +1,7 @@
package com.xs.ai.data;
public enum BookingStatus {
CONFIRMED, COMPLETED, CANCELLED
}

35
src/main/java/com/xs/ai/data/Customer.java

@ -0,0 +1,35 @@
package com.xs.ai.data;
import java.util.ArrayList;
import java.util.List;
public class Customer {
private String name;
private List<Booking> bookings = new ArrayList<>();
public Customer() {
}
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Booking> getBookings() {
return bookings;
}
public void setBookings(List<Booking> bookings) {
this.bookings = bookings;
}
}

72
src/main/java/com/xs/ai/services/BookingTools.java

@ -0,0 +1,72 @@
package com.xs.ai.services;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.xs.ai.data.BookingStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;
import org.springframework.core.NestedExceptionUtils;
import java.time.LocalDate;
import java.util.function.Function;
@Configuration
public class BookingTools {
private static final Logger logger = LoggerFactory.getLogger(BookingTools.class);
@Autowired
private FlightBookingService flightBookingService;
public record BookingDetailsRequest(String bookingNumber, String name) {
}
public record ChangeBookingDatesRequest(String bookingNumber, String name, String date, String from, String to) {
}
public record CancelBookingRequest(String bookingNumber, String name) {
}
@JsonInclude(Include.NON_NULL)
public record BookingDetails(String bookingNumber, String name, LocalDate date, BookingStatus bookingStatus,
String from, String to, String bookingClass) {
}
@Bean
@Description("获取机票预定详细信息")
public Function<BookingDetailsRequest, BookingDetails> getBookingDetails() {
return request -> {
try {
return flightBookingService.getBookingDetails(request.bookingNumber(), request.name());
}
catch (Exception e) {
logger.warn("Booking details: {}", NestedExceptionUtils.getMostSpecificCause(e).getMessage());
return new BookingDetails(request.bookingNumber(), request.name(), null, null, null, null, null);
}
};
}
@Bean
@Description("修改机票预定日期")
public Function<ChangeBookingDatesRequest, String> changeBooking() {
return request -> {
flightBookingService.changeBooking(request.bookingNumber(), request.name(), request.date(), request.from(),
request.to());
return "";
};
}
@Bean
@Description("取消机票预定")
public Function<CancelBookingRequest, String> cancelBooking() {
return request -> {
flightBookingService.cancelBooking(request.bookingNumber(), request.name());
return "";
};
}
}

84
src/main/java/com/xs/ai/services/CustomerSupportAssistant.java

@ -0,0 +1,84 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xs.ai.services;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import java.time.LocalDate;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;
/**
* * @author Christian Tzolov
*/
@Service
public class CustomerSupportAssistant {
private final ChatClient chatClient;
public CustomerSupportAssistant(ChatClient.Builder modelBuilder, VectorStore vectorStore, ChatMemory chatMemory) {
// @formatter:off
this.chatClient = modelBuilder
.defaultSystem("""
您是Funnair航空公司的客户聊天支持代理请以友好乐于助人且愉快的方式来回复
您正在通过在线聊天系统与客户互动
在提供有关预订或取消预订的信息之前您必须始终
从用户处获取以下信息预订号客户姓名
在询问用户之前请检查消息历史记录以获取此信息
在更改预订之前您必须确保条款允许这样做
如果更改需要收费您必须在继续之前征得用户同意
使用提供的功能获取预订详细信息更改预订和取消预订
如果需要可以调用相应函数调用完成辅助动作
请讲中文
今天的日期是 {current_date}.
""")
.defaultAdvisors(
new PromptChatMemoryAdvisor(chatMemory), // Chat Memory
// new VectorStoreChatMemoryAdvisor(vectorStore)),
new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()), // RAG
// new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()
// .withFilterExpression("'documentType' == 'terms-of-service' && region in ['EU', 'US']")),
new LoggingAdvisor())
.defaultFunctions("getBookingDetails", "changeBooking", "cancelBooking") // FUNCTION CALLING
.build();
// @formatter:on
}
public Flux<String> chat(String chatId, String userMessageContent) {
return this.chatClient.prompt()
.system(s -> s.param("current_date", LocalDate.now().toString()))
.user(userMessageContent)
.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId).param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
.stream()
.content();
}
}

101
src/main/java/com/xs/ai/services/FlightBookingService.java

@ -0,0 +1,101 @@
package com.xs.ai.services;
import com.xs.ai.data.*;
import org.springframework.stereotype.Service;
import com.xs.ai.services.BookingTools.BookingDetails;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@Service
public class FlightBookingService {
private final BookingData db;
public FlightBookingService() {
db = new BookingData();
initDemoData();
}
private void initDemoData() {
List<String> names = List.of("徐庶", "诸葛", "百里", "楼兰", "庄周");
List<String> airportCodes = List.of("北京", "上海", "广州", "深圳", "杭州", "南京", "青岛", "成都", "武汉", "西安", "重庆", "大连",
"天津");
Random random = new Random();
var customers = new ArrayList<Customer>();
var bookings = new ArrayList<Booking>();
for (int i = 0; i < 5; i++) {
String name = names.get(i);
String from = airportCodes.get(random.nextInt(airportCodes.size()));
String to = airportCodes.get(random.nextInt(airportCodes.size()));
BookingClass bookingClass = BookingClass.values()[random.nextInt(BookingClass.values().length)];
Customer customer = new Customer();
customer.setName(name);
LocalDate date = LocalDate.now().plusDays(2 * (i + 1));
Booking booking = new Booking("10" + (i + 1), date, customer, BookingStatus.CONFIRMED, from, to,
bookingClass);
customer.getBookings().add(booking);
customers.add(customer);
bookings.add(booking);
}
// Reset the database on each start
db.setCustomers(customers);
db.setBookings(bookings);
}
// 获取所有已预订航班
public List<BookingDetails> getBookings() {
return db.getBookings().stream().map(this::toBookingDetails).toList();
}
// 根据编号+姓名查询航班
private Booking findBooking(String bookingNumber, String name) {
return db.getBookings()
.stream()
.filter(b -> b.getBookingNumber().equalsIgnoreCase(bookingNumber))
.filter(b -> b.getCustomer().getName().equalsIgnoreCase(name))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Booking not found"));
}
// 查询航班详情
public BookingDetails getBookingDetails(String bookingNumber, String name) {
var booking = findBooking(bookingNumber, name);
return toBookingDetails(booking);
}
// 更改预定航班
public void changeBooking(String bookingNumber, String name, String newDate, String from, String to) {
var booking = findBooking(bookingNumber, name);
if (booking.getDate().isBefore(LocalDate.now().plusDays(1))) {
throw new IllegalArgumentException("Booking cannot be changed within 24 hours of the start date.");
}
booking.setDate(LocalDate.parse(newDate));
booking.setFrom(from);
booking.setTo(to);
}
// 取消预定航班
public void cancelBooking(String bookingNumber, String name) {
var booking = findBooking(bookingNumber, name);
if (booking.getDate().isBefore(LocalDate.now().plusDays(2))) {
throw new IllegalArgumentException("Booking cannot be cancelled within 48 hours of the start date.");
}
booking.setBookingStatus(BookingStatus.CANCELLED);
}
private BookingDetails toBookingDetails(Booking booking) {
return new BookingDetails(booking.getBookingNumber(), booking.getCustomer().getName(), booking.getDate(),
booking.getBookingStatus(), booking.getFrom(), booking.getTo(), booking.getBookingClass().toString());
}
}

20
src/main/java/com/xs/ai/services/LoggingAdvisor.java

@ -0,0 +1,20 @@
package com.xs.ai.services;
import org.springframework.ai.chat.client.RequestResponseAdvisor;
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
import java.util.Map;
public class LoggingAdvisor implements RequestResponseAdvisor {
@Override
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
System.out.println("Request: " + request);
return request;
}
@Override
public int getOrder() {
return 0;
}
}

4
src/main/resources/application.properties

@ -0,0 +1,4 @@
spring.ai.dashscope.api-key= ${ALI_AI_KEY}
spring.ai.dashscope.chat.options.model=deepseek-v3

33
src/main/resources/application.yml

@ -0,0 +1,33 @@
#xushu:
# # 官方(自行配置)
# openai:
# key: ${OPENAI_KEY}
# url: ${OPENAI_URL}
# # 中转1
# aicore:
# key: ${OPEN_AI_KEY}
# url: ${OPEN_AI_URL}
# # 中转2 神经网络 算法模型,精通各个目标检测算法和熟悉神经网络基本架构
# gaochao:
# key: ${OPEN_AI_KEY_GAOCHAO}
# url: ${OPEN_AI_URL_GAOCHAO}
#spring:
# ai:
# openai:
# api-key:
# base-url: https://api.deepseek.com
# chat:
# options:
# model: deepseek-chat
spring:
ai:
ollama:
embedding:
model: shaw/dmeta-embedding-zh
chat:
model: deepseek-r1:8b

13
src/main/resources/rag/terms-of-service.txt

@ -0,0 +1,13 @@
本服务条款适用于您对 Funnair 的体验。预订航班,即表示您同意这些条款。
1. 预订航班
- 通过我们的网站或移动应用程序预订。
- 预订时需要全额付款。
- 确保个人信息(姓名、ID 等)的准确性,因为更正可能会产生 25 的费用。
2. 更改预订
- 允许在航班起飞前 24 小时更改。
- 通过在线更改或联系我们的支持人员。
- 改签费:经济舱 50,豪华经济舱 30,商务舱免费。
3. 取消预订
- 最晚在航班起飞前 48 小时取消。
- 取消费用:经济舱 75 美元,豪华经济舱 50 美元,商务舱 25 美元。
- 退款将在 7 个工作日内处理。

13
src/test/java/com/xs/ai/SpringAiDemoApplicationTests.java

@ -0,0 +1,13 @@
package com.xs.ai;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringAiDemoApplicationTests {
@Test
void contextLoads() {
}
}
Loading…
Cancel
Save