spring-ai-alibaba使用Agent实现智能机票助手

news2025/4/19 8:11:02

示例目标是使用 Spring AI Alibaba 框架开发一个智能机票助手,它可以帮助消费者完成机票预定、问题解答、机票改签、取消等动作,具体要求为:

  • 基于 AI 大模型与用户对话,理解用户自然语言表达的需求
  • 支持多轮连续对话,能在上下文中理解用户意图
  • 理解机票操作相关的术语与规范并严格遵守,如航空法规、退改签规则等
  • 在必要时可调用工具辅助完成任务
使用 RAG 增加机票退改签规则

基于以上架构图,应用是由 AI 模型理解用户问题,决策下一步动作、驱动业务流程。但任何一个通用的大模型都能帮我们解决机票相关的问题吗?依赖模型的决策是可靠的吗?比如有用户提出了机票改签的诉求,模型一定是能够很好的理解用户的意图的,这点没有疑问。但它怎么知道当前用户符不符合退票规则呢?要知道每个航空公司的改签规则可能都是不一样的;它怎么知道改签手续费的规定那?在这样一个可能带来经济纠纷、法律风险的应用场景下,AI模型必须要知道改签规则的所有细节,并逐条确认用户信息符合规则后,才能最终作出是否改签的决策。

很显然,单纯依赖 AI 模型本身并不能替我们完成上面的要求,这个时候就要用到 RAG(检索增强)模式了。通过 RAG 我们可以把机票退改签相关的领域知识输入给应用和 AI 模型,让 AI 结合这些规则与要求辅助决策

使用 Function Calling 执行业务动作

AI 智能体可以帮助应用理解用户需求并作出决策,但是它没法代替应用完成决策的执行,决策的执行还是要由应用自己完成,这一点和传统应用并没有区别,不论是智能化的还是预先编排好的应用,都是要由应用本身去调用函数修改数据库记录实现数据持久化。

通过 Spring AI 框架,我们可以将模型的决策转换为对某个具体函数的调用,从而完成机票的最终改签或者退票动作,将用户数据写入数据库,这就是我们前面提到的 Function Calling 模式。

使用 Chat Memory 增加多轮对话能力

最后一点是关于多轮连续对话的支持,我们要记住一点,大模型是无状态的,它看到的只有当前这一轮对话的内容。因此如果要支持多轮对话效果,需要应用每次都要把之前的对话上下文保留下来,并与最新的问题一并作为 prompt 发送给模型。这时,我们可以利用 Spring AI Alibaba 提供的内置 Conversation Memory 支持,方便的维护对话上下文。

总结起来,我们在这个智能机票助手应用中用到了 Spring AI Alibaba 的核心如下能力:

  1. 基本模型对话能力,通过 Chat Model API 与阿里云通义模型交互
  2. Prompt 管理能力
  3. Chat Memory 聊天记忆,支持多轮对话
  4. RAG、Vector Store,机票预定、改签、退票等相关规则

1、jdk17 ,spring-ai-alibaba版本 1.0.0-M6.1,使用阿里云大模型,若使用其他模型添加对应依赖即可

pom文件

	<properties>
		<java.version>17</java.version>
		<vaadin.version>24.4.7</vaadin.version>
		<maven-deploy-plugin.version>3.1.1</maven-deploy-plugin.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>com.alibaba.cloud.ai</groupId>
			<artifactId>spring-ai-alibaba-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

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

		<!-- Other spring dependencies -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
		</dependency>
	</dependencies>

2、application.properties 内容如下

# spring.ai.chat.client.enabled=false
server.port=19000
spring.threads.virtual.enabled=true

spring.mvc.static-path-pattern=/templates/**
spring.thymeleaf.cache=false
###################
# Anthropic Claude 3
###################

# spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
# spring.ai.openai.chat.options.model=llama3-70b-8192
# spring.ai.anthropic.chat.options.model=claude-3-5-sonnet-20240620


###################
# Groq
###################

# spring.ai.openai.api-key=${GROQ_API_KEY}
# spring.ai.openai.base-url=https://api.groq.com/openai
# spring.ai.openai.chat.options.model=llama3-70b-8192

###################
# dashscope
###################
spring.ai.dashscope.api-key=${AI_DASHSCOPE_API_KEY}
spring.ai.dashscope.chat.options.model=qwen-plus
spring.ai.dashscope.embedding.enabled=true
spring.ai.dashscope.embedding.options.model=text-embedding-v2

###################
# OpenAI
###################
# spring.ai.openai.chat.options.functions=getBookingDetails,changeBooking,cancelBooking
# spring.ai.openai.chat.enabled=false

# Disable the OpenAI embedding when the local huggingface embedding (e.g. spring-ai-transformers-spring-boot-starter) is used.
# spring.ai.openai.embedding.enabled=false

###################
# Azure OpenAI
###################
# spring.ai.azure.openai.api-key=${AZURE_OPENAI_API_KEY}
# spring.ai.azure.openai.endpoint=${AZURE_OPENAI_ENDPOINT}
# spring.ai.azure.openai.chat.options.deployment-name=gpt-4o

###################
# Mistral AI
###################

# spring.ai.mistralai.api-key=${MISTRAL_AI_API_KEY}
# spring.ai.mistralai.chat.options.model=mistral-small-latest

# spring.ai.mistralai.chat.options.model=mistral-small-latest
# spring.ai.mistralai.chat.options.functions=getBookingDetails,changeBooking,cancelBooking
# # spring.ai.retry.on-client-errors=true
# # spring.ai.retry.exclude-on-http-codes=429

###################
# Vertex AI Gemini
###################

# spring.ai.vertex.ai.gemini.project-id=${VERTEX_AI_GEMINI_PROJECT_ID}
# spring.ai.vertex.ai.gemini.location=${VERTEX_AI_GEMINI_LOCATION}
# spring.ai.vertex.ai.gemini.chat.options.model=gemini-1.5-pro-001
# # spring.ai.vertex.ai.gemini.chat.options.model=gemini-1.5-flash-001
# spring.ai.vertex.ai.gemini.chat.options.transport-type=REST

# spring.ai.vertex.ai.gemini.chat.options.functions=getBookingDetails,changeBooking,cancelBooking

###################
#  Milvus Vector Store
###################
# Change the dimentions to 384 if the local huggingface embedding (e.g. spring-ai-transformers-spring-boot-starter) is used.
# spring.ai.vectorstore.milvus.embedding-dimension=384

###################
# PGVector
###################
# spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
# spring.datasource.username=postgres
# spring.datasource.password=postgres

###################
# QDrant
###################
# spring.ai.vectorstore.qdrant.host=localhost
# spring.ai.vectorstore.qdrant.port=6334

###################
# Chroma
###################
# spring.ai.vectorstore.chroma.client.host=http://localhost
# spring.ai.vectorstore.chroma.client.port=8000

3、知识库文本文件  terms-of-service.txt  内容如下

These Terms of Service govern your experience with Funnair. By booking a flight, you agree to these terms.

1. Booking Flights
- Book via our website or mobile app.
- Full payment required at booking.
- Ensure accuracy of personal information (Name, ID, etc.) as corrections may incur a $25 fee.

2. Changing Bookings
- Changes allowed up to 24 hours before flight.
- Change via online or contact our support.
- Change fee: $50 for Economy, $30 for Premium Economy, Free for Business Class.

3. Cancelling Bookings
- Cancel up to 48 hours before flight.
- Cancellation fees: $75 for Economy, $50 for Premium Economy, $25 for Business Class.
- Refunds processed within 7 business days.

4、初始化向量库和对话记忆等

package ai.spring.demo.ai.playground.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.web.client.RestClient;

@Configuration
public class InitConfig {
    private static final Logger logger = LoggerFactory.getLogger(InitConfig.class);
    @Bean
    public VectorStore vectorStore(EmbeddingModel embeddingModel) {
        //初始化向量库

        return SimpleVectorStore.builder(embeddingModel).build();
    }

    @Bean
    CommandLineRunner ingestTermOfServiceToVectorStore(VectorStore vectorStore,
                                                       @Value("classpath:rag/terms-of-service.txt") Resource termsOfServiceDocs) {
        //加载机票票务相关知识到向量库
        return args -> {
            // Ingest the document into the vector store
            vectorStore.write(new TokenTextSplitter().transform(new TextReader(termsOfServiceDocs).read()));

            vectorStore.similaritySearch("Cancelling Bookings").forEach(doc -> {
                logger.info("Similar Document: {}", doc.getText());
            });
        };
    }

    @Bean
    public ChatMemory chatMemory() {
        //对话记忆
        return new InMemoryChatMemory();
    }

    @Bean
    @ConditionalOnMissingBean
    public RestClient.Builder restClientBuilder() {
        return RestClient.builder();
    }

}

5、机票信息、机票类型、机票状态、等相关实体类

package ai.spring.demo.ai.playground.data;

public enum BookingClass {
//	机票类型 头等 商务
    FIRST ,BUSINESS

}
package ai.spring.demo.ai.playground.data;

public enum BookingStatus {
//机票状态 已确认  已使用 已取消
	CONFIRMED, COMPLETED, CANCELLED

}

package ai.spring.demo.ai.playground.data;

import java.time.LocalDate;

/**
 * 机票信息
 */
public class Booking {

    /**
     * 订单号
     */
	private String bookingNumber;

    /**
     * 订单日期
     */
	private LocalDate date;


    /**
     * 购票信息
     */
	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 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;
	}

}
package ai.spring.demo.ai.playground.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;
	}

}

下面的类 是模拟数据库

package ai.spring.demo.ai.playground.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;
	}

}

6、机票服务类(包含数据初始化、退票、改签等业务方法)、智能体工具和智能体实现

package ai.spring.demo.ai.playground.services;

import ai.spring.demo.ai.playground.data.*;
import ai.spring.demo.ai.playground.services.BookingTools.BookingDetails;

import org.springframework.stereotype.Service;

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 < 13; 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);
	}

	/**
	 * 获取数据库所有票务信息
	 * @return
	 */
	public List<BookingDetails> getBookings() {
		return db.getBookings().stream().map(this::toBookingDetails).toList();
	}

	/**
	 * 根据订单号 合购票者姓名查找票务信息
	 * @param bookingNumber
	 * @param name
	 * @return
	 */
	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"));
	}

	/**
	 * 根据订单号 合购票者姓名查找票务信息
	 * @param bookingNumber
	 * @param name
	 * @return
	 */
	public BookingDetails getBookingDetails(String bookingNumber, String name) {
		var booking = findBooking(bookingNumber, name);
		return toBookingDetails(booking);
	}

	/**
	 * 改签票务
	 * @param bookingNumber
	 * @param name
	 * @param newDate
	 * @param from
	 * @param to
	 */
	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);
	}

	/**
	 * 退票
	 * @param bookingNumber
	 * @param name
	 */
	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());
	}

}
package ai.spring.demo.ai.playground.services;

import java.time.LocalDate;
import java.util.function.Function;

import ai.spring.demo.ai.playground.data.BookingStatus;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
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;

@Configuration
public class BookingTools {

	private static final Logger logger = LoggerFactory.getLogger(BookingTools.class);

	@Autowired
	private FlightBookingService flightBookingService;

	/**
	 * 获取票务信息工具请求
	 * @param bookingNumber
	 * @param name
	 */
	public record BookingDetailsRequest(String bookingNumber, String name) {
	}

	/**
	 * 改签票务工具请求
	 * @param bookingNumber
	 * @param name
	 * @param date
	 * @param from
	 * @param to
	 */
	public record ChangeBookingDatesRequest(String bookingNumber, String name, String date, String from, String to) {
	}

	/**
	 * 退票工具请求
	 * @param bookingNumber
	 * @param name
	 */
	public record CancelBookingRequest(String bookingNumber, String name) {
	}

	/**
	 * 获取票务信息工具返回
	 * @param bookingNumber
	 * @param name
	 * @param date
	 * @param bookingStatus
	 * @param from
	 * @param to
	 * @param bookingClass
	 */
	@JsonInclude(Include.NON_NULL)
	public record BookingDetails(String bookingNumber, String name, LocalDate date, BookingStatus bookingStatus,
			String from, String to, String bookingClass) {
	}

	/**
	 * 获取机票预定详细信息 工具
	 * @return
	 */
	@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);
			}
		};
	}

	/**
	 * 修改机票预定日期工具
	 * @return
	 */
	@Bean
	@Description("修改机票预定日期")
	public Function<ChangeBookingDatesRequest, String> changeBooking() {
		return request -> {
			flightBookingService.changeBooking(request.bookingNumber(), request.name(), request.date(), request.from(),
					request.to());
			return "";
		};
	}

	/**
	 * 取消机票预定 工具
	 * @return
	 */
	@Bean
	@Description("取消机票预定")
	public Function<CancelBookingRequest, String> cancelBooking() {
		return request -> {
			flightBookingService.cancelBooking(request.bookingNumber(), request.name());
			return "";
		};
	}

}

/*
 * 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 ai.spring.demo.ai.playground.services;

import java.time.LocalDate;

import reactor.core.publisher.Flux;

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.client.advisor.SimpleLoggerAdvisor;
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 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;

/**
 * 智能机票助手 agent
 */
@Service
public class CustomerSupportAssistant {

	private final ChatClient chatClient;

	public CustomerSupportAssistant(ChatClient.Builder modelBuilder, VectorStore vectorStore, ChatMemory chatMemory) {

		// 初始化 Agent 智能体
		this.chatClient = modelBuilder
				.defaultSystem("""
						您是“Funnair”航空公司的客户聊天支持代理。请以友好、乐于助人且愉快的方式来回复。
						您正在通过在线聊天系统与客户互动。
						您能够支持已有机票的预订详情查询、机票日期改签、机票预订取消等操作,其余功能将在后续版本中添加,如果用户问的问题不支持请告知详情。
					   在提供有关机票预订详情查询、机票日期改签、机票预订取消等操作之前,您必须始终从用户处获取以下信息:预订号、客户姓名。
					   在询问用户之前,请检查消息历史记录以获取预订号、客户姓名等信息,尽量避免重复询问给用户造成困扰。
					   在更改预订之前,您必须确保条款允许这样做。
					   如果更改需要收费,您必须在继续之前征得用户同意。
					   使用提供的功能获取预订详细信息、更改预订和取消预订。
					   如果需要,您可以调用相应函数辅助完成。
					   请讲中文。
					   今天的日期是 {current_date}.
					""")
				.defaultAdvisors(
						//对话记忆
						new PromptChatMemoryAdvisor(chatMemory), // Chat Memory
						// new VectorStoreChatMemoryAdvisor(vectorStore)),
					    //RAG知识库
						new QuestionAnswerAdvisor(vectorStore, SearchRequest.builder().topK(4).similarityThresholdAll().build()), // RAG
						// new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()
						// 	.withFilterExpression("'documentType' == 'terms-of-service' && region in ['EU', 'US']")),

						// logger
						new SimpleLoggerAdvisor()
				)
						//定义工具  对应 BookingTools中的三个方法
				.defaultFunctions("getBookingDetails", "changeBooking", "cancelBooking") // FUNCTION CALLING

				.build();

	}

	public Flux<String> chat(String chatId, String userMessageContent) {

		return this.chatClient.prompt()
//				current_date  defaultSystem中的变量
			.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();
	}

}

7、机票列表接口和智能体对话接口

package ai.spring.demo.ai.playground.client;


import ai.spring.demo.ai.playground.services.BookingTools.BookingDetails;
import ai.spring.demo.ai.playground.services.FlightBookingService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

@Controller
@RequestMapping("/")
public class BookingController {

	private final FlightBookingService flightBookingService;

	public BookingController(FlightBookingService flightBookingService) {
		this.flightBookingService = flightBookingService;
	}

	@RequestMapping("/")
	public String index() {
		return "index";
	}

	/**
	 * 获取数据库所有票务信息
	 * @return
	 */
	@RequestMapping("/api/bookings")
	@ResponseBody
	public List<BookingDetails> getBookings() {
		return flightBookingService.getBookings();
	}

}

package ai.spring.demo.ai.playground.client;

import ai.spring.demo.ai.playground.services.CustomerSupportAssistant;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;


@RequestMapping("/api/assistant")
@RestController
public class AssistantController {

	private final CustomerSupportAssistant agent;

	public AssistantController(CustomerSupportAssistant agent) {
		this.agent = agent;
	}

	@RequestMapping(path="/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
	public Flux<String> chat(String chatId, String userMessage) {
		return agent.chat(chatId, userMessage);
	}

}

8、页面展示如下

访问  http://127.0.0.1:19000/

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2336282.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux多线(进)程编程——(7)消息队列

前言 现在修真界大家的沟通手段已经越来越丰富了&#xff0c;有了匿名管道&#xff0c;命名管道&#xff0c;共享内存等多种方式。但是随着深入使用人们逐渐发现了这些传音术的局限性。 匿名管道&#xff1a;只能在有血缘关系的修真者&#xff08;进程&#xff09;间使用&…

从服务器多线程批量下载文件到本地

1、客户端安装 aria2 下载地址&#xff1a;aria2 解压文件&#xff0c;然后将文件目录添加到系统环境变量Path中&#xff0c;然后打开cmd&#xff0c;输入&#xff1a;aria2c 文件地址&#xff0c;就可以下载文件了 2、服务端配置nginx文件服务器 server {listen 8080…

循环神经网络 - 深层循环神经网络

如果将深度定义为网络中信息传递路径长度的话&#xff0c;循环神经网络可以看作既“深”又“浅”的网络。 一方面来说&#xff0c;如果我们把循环网络按时间展开&#xff0c;长时间间隔的状态之间的路径很长&#xff0c;循环网络可以看作一个非常深的网络。 从另一方面来 说&…

linux运维篇-Ubuntu(debian)系操作系统创建源仓库

适用范围 适用于Ubuntu&#xff08;Debian&#xff09;及其衍生版本的linux系统 例如&#xff0c;国产化操作系统kylin-desktop-v10 简介 先来看下我们需要创建出来的仓库目录结构 Deb_conf_test apt源的主目录 conf 配置文件存放目录 conf目录下存放两个配置文件&…

深度学习之微积分

2.4.1 导数和微分 2.4.2 偏导数 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/17227e00adb14472902baba4da675aed.png 2.4.3 梯度 具体证明&#xff0c;矩阵-向量积

20242817李臻《Linux⾼级编程实践》第7周

20242817李臻《Linux⾼级编程实践》第7周 一、AI对学习内容的总结 第八章&#xff1a;多线程编程 8.1 多线程概念 进程与线程的区别&#xff1a; 进程是资源分配单位&#xff0c;拥有独立的地址空间、全局变量、打开的文件等。线程是调度单位&#xff0c;在同一进程内的线程…

浙江大学:DeepSeek如何引领智慧医疗的革新之路?|48页PPT下载方法

导 读INTRODUCTION 随着人工智能技术的飞速发展&#xff0c;DeepSeek等大模型正在引领医疗行业进入一个全新的智慧医疗时代。这些先进的技术不仅正在改变医疗服务的提供方式&#xff0c;还在提高医疗质量和效率方面展现出巨大潜力。 想象一下&#xff0c;当你走进医院&#xff…

Android基础彻底解析-APK入口点,xml,组件,脱壳,逆向

第一章:引言与背景 Android逆向工程,作为一种深入分析Android应用程序的技术,主要目的就是通过分析应用的代码、资源和行为来理解其功能、结构和潜在的安全问题。它不仅仅是对应用进行破解或修改,更重要的是帮助开发者、研究人员和安全人员发现并解决安全隐患。 本文主要对…

ubuntu 2204 安装 vcs 2018

安装评估 系统 : Ubuntu 22.04.1 LTS 磁盘 : ubuntu 自身占用了 9.9G , 按照如下步骤 安装后 , 安装后的软件 占用 13.1G 仓库 : 由于安装 libpng12-0 , 添加了一个仓库 安装包 : 安装了多个包(lsb及其依赖包 libpng12-0)安装步骤 参考 ubuntu2018 安装 vcs2018 安装该…

逆向|中国产业政策大数据平台|请求体加密

2025-04-11 逆向地址:aHR0cDovL3poZW5nY2UuMmIuY24v 打开开发者工具出现debugger,直接注入脚本过掉无限debugger let aaa Function.prototype.constructor; Function.prototype.constructor function (params) { if(params ‘debugger’){ console.log(params); return null…

游戏引擎学习第226天

引言&#xff0c;计划 我们目前的目标是开始构建“元游戏”结构。所谓元游戏&#xff0c;指的是不直接属于核心玩法本身&#xff0c;但又是游戏体验不可或缺的一部分&#xff0c;比如主菜单、标题画面、存档选择、选项设置、过场动画等。我们正在慢慢将这些系统结构搭建起来。…

Notepad++安装Markdown实时预览插件

具体操作 打开notepad -> 插件 -> 插件管理 -> 可用 -> “Markdown Panel” -> 安装&#xff0c;安装完成后工具栏点击"Markdown Panel"按钮。 注意&#xff1a;由于网络等原因可能安装失败 导致工具栏没出现""Markdown Panel"按钮&am…

Mysql-视图和存储过程

视图 1.介绍 视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。 通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作就落在创建这条…

stm32面试

数据结构相关问题 stm32面试 数据结构相关问题 目录基础数据结构树与图排序与查找算法 Linux相关问题Linux系统基础Linux命令与脚本Linux网络与服务 操作系统相关问题操作系统基础概念操作系统调度算法操作系统同步与通信 STM32相关问题STM32硬件基础STM32编程与开发STM32应用与…

202524 | 分布式事务

分布式事务&#xff08;Distributed Transaction&#xff09; 分布式事务是指跨多个数据库、服务或系统节点的事务操作&#xff0c;要求所有参与方要么全部成功提交&#xff0c;要么全部回滚&#xff0c;保证数据一致性。 1. 为什么需要分布式事务&#xff1f; 在单体应用中&…

在 macOS 上修改 最大文件描述符限制(Too many open files) 和 网络端口相关参数 需要调整系统级配置的详细步骤

在 macOS 上修改 最大文件描述符限制&#xff08;Too many open files&#xff09; 和 网络端口相关参数 需要调整系统级配置。以下是详细步骤&#xff1a; 在 macOS 上修改 最大文件描述符限制&#xff08;Too many open files&#xff09; 和 网络端口相关参数 需要调整系统级…

通过Arduino IDE向闪存文件系统上传文件

注意&#xff1a;适用于Arduino IDE 2.0版本以上。对于Arduino IDE版本在2.0以下的请参考太极创客的教程&#xff1a;http://www.taichi-maker.com/homepage/esp8266-nodemcu-iot/iot-c/spiffs/upload-files/。 1. 下载脚本文件 下载地址&#xff1a;https://github.com/earl…

leetcode 121. Best Time to Buy and Sell Stock

题目描述 本题属于动态规划类问题。 dp数组的含义 dp[i][0]表示从第0天到第i天为止&#xff0c;处于持有股票的状态下&#xff0c;账户里的最大金额。 dp[i][1]表示从第0天到第i天为止&#xff0c;处于不持有股票的状态下&#xff0c;账户里的最大金额。 按照这个定义dp[n-…

【Docker-13】Docker Container容器

Docker Container&#xff08;容器&#xff09; 一、什么是容器&#xff1f; 通俗地讲&#xff0c;容器是镜像的运行实体。镜像是静态的只读文件&#xff0c;而容器带有运行时需要的可写文件层&#xff0c;并且容器中的进程属于运行状态。即容器运行着真正的应用进程。容器有…

蓝宝石狼组织升级攻击工具包,利用新型紫水晶窃密软件瞄准能源企业

网络安全专家发现&#xff0c;被称为"蓝宝石狼"&#xff08;Sapphire Werewolf&#xff09;的威胁组织正在使用升级版"紫水晶"&#xff08;Amethyst&#xff09;窃密软件&#xff0c;对能源行业企业发起复杂攻击活动。此次攻击标志着该组织能力显著提升&am…