创建一个基于Spring AI的智能旅行简单案例

创建一个基于Spring AI的智能旅行助手案例,展示Function Calling、LLM和AI Agent的综合应用。

项目概述

这个智能旅行助手可以:

  • 查询天气信息
  • 推荐旅行景点
  • 计算两地距离
  • 提供综合旅行建议

1. 项目依赖配置

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        <version>1.0.0-M2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

2. 配置文件

yaml 复制代码
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4
          temperature: 0.7
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create-drop

3. Function定义和实现

天气查询Function

java 复制代码
@Component
public class WeatherFunction implements Function<WeatherFunction.Request, WeatherFunction.Response> {
    
    @JsonClassDescription("获取指定城市的天气信息")
    public record Request(
        @JsonProperty(required = true, value = "city") 
        @JsonPropertyDescription("城市名称") 
        String city
    ) {}
    
    @JsonClassDescription("天气信息响应")
    public record Response(
        String city,
        String temperature,
        String condition,
        String humidity,
        String windSpeed
    ) {}
    
    @Override
    public Response apply(Request request) {
        // 模拟天气API调用
        return switch (request.city().toLowerCase()) {
            case "北京" -> new Response("北京", "25°C", "晴朗", "45%", "15km/h");
            case "上海" -> new Response("上海", "28°C", "多云", "60%", "12km/h");
            case "广州" -> new Response("广州", "32°C", "雷阵雨", "80%", "8km/h");
            default -> new Response(request.city(), "22°C", "未知", "50%", "10km/h");
        };
    }
}

景点推荐Function

java 复制代码
@Component
public class AttractionFunction implements Function<AttractionFunction.Request, AttractionFunction.Response> {
    
    @JsonClassDescription("获取指定城市的旅游景点推荐")
    public record Request(
        @JsonProperty(required = true, value = "city") 
        @JsonPropertyDescription("城市名称") 
        String city,
        
        @JsonProperty(value = "category")
        @JsonPropertyDescription("景点类型:历史文化、自然风光、现代娱乐") 
        String category
    ) {}
    
    @JsonClassDescription("景点推荐响应")
    public record Response(
        String city,
        List<Attraction> attractions
    ) {}
    
    public record Attraction(
        String name,
        String description,
        String rating,
        String ticketPrice
    ) {}
    
    @Override
    public Response apply(Request request) {
        List<Attraction> attractions = getAttractionsByCity(request.city(), request.category());
        return new Response(request.city(), attractions);
    }
    
    private List<Attraction> getAttractionsByCity(String city, String category) {
        Map<String, List<Attraction>> cityAttractions = Map.of(
            "北京", List.of(
                new Attraction("故宫博物院", "明清两代皇宫,世界文化遗产", "4.8", "60元"),
                new Attraction("天安门广场", "世界最大城市广场之一", "4.7", "免费"),
                new Attraction("长城", "世界七大奇迹之一", "4.9", "45元")
            ),
            "上海", List.of(
                new Attraction("外滩", "上海标志性景观带", "4.6", "免费"),
                new Attraction("东方明珠", "上海地标性建筑", "4.5", "180元"),
                new Attraction("豫园", "江南古典园林", "4.4", "40元")
            )
        );
        
        return cityAttractions.getOrDefault(city, 
            List.of(new Attraction("当地特色景点", "待发现的美丽之地", "4.0", "未知")));
    }
}

距离计算Function

java 复制代码
@Component
public class DistanceFunction implements Function<DistanceFunction.Request, DistanceFunction.Response> {
    
    @JsonClassDescription("计算两个城市之间的距离")
    public record Request(
        @JsonProperty(required = true, value = "from") 
        @JsonPropertyDescription("出发城市") 
        String from,
        
        @JsonProperty(required = true, value = "to") 
        @JsonPropertyDescription("目标城市") 
        String to
    ) {}
    
    @JsonClassDescription("距离计算响应")
    public record Response(
        String from,
        String to,
        String distance,
        String travelTime
    ) {}
    
    @Override
    public Response apply(Request request) {
        // 模拟距离计算
        String key = request.from() + "-" + request.to();
        Map<String, Response> distances = Map.of(
            "北京-上海", new Response("北京", "上海", "1213公里", "高铁4.5小时"),
            "上海-北京", new Response("上海", "北京", "1213公里", "高铁4.5小时"),
            "北京-广州", new Response("北京", "广州", "2129公里", "高铁8小时"),
            "广州-北京", new Response("广州", "北京", "2129公里", "高铁8小时")
        );
        
        return distances.getOrDefault(key, 
            new Response(request.from(), request.to(), "未知距离", "未知时间"));
    }
}

4. AI Agent核心服务

java 复制代码
@Service
public class TravelAgentService {
    
    private final ChatClient chatClient;
    
    public TravelAgentService(ChatClient.Builder chatClientBuilder,
                            WeatherFunction weatherFunction,
                            AttractionFunction attractionFunction,
                            DistanceFunction distanceFunction) {
        this.chatClient = chatClientBuilder
            .defaultSystem("""
                你是一个专业的旅行助手,可以帮助用户规划旅行。
                你有以下能力:
                1. 查询城市天气信息
                2. 推荐旅游景点
                3. 计算城市间距离
                
                请根据用户的需求,主动调用相关功能并提供综合性的旅行建议。
                回答要友好、详细且实用。
                """)
            .defaultFunctions("weatherFunction", "attractionFunction", "distanceFunction")
            .build();
    }
    
    public String planTravel(String userRequest) {
        return chatClient.prompt()
            .user(userRequest)
            .call()
            .content();
    }
    
    public Flux<String> planTravelStream(String userRequest) {
        return chatClient.prompt()
            .user(userRequest)
            .stream()
            .content();
    }
}

5. 旅行记录实体和服务

java 复制代码
@Entity
@Table(name = "travel_plans")
public class TravelPlan {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String userId;
    private String destination;
    private String planContent;
    private LocalDateTime createdAt;
    
    // 构造函数、getter、setter省略
}

@Repository
public interface TravelPlanRepository extends JpaRepository<TravelPlan, Long> {
    List<TravelPlan> findByUserId(String userId);
}

@Service
public class TravelPlanService {
    
    private final TravelPlanRepository repository;
    
    public TravelPlanService(TravelPlanRepository repository) {
        this.repository = repository;
    }
    
    public TravelPlan savePlan(String userId, String destination, String content) {
        TravelPlan plan = new TravelPlan();
        plan.setUserId(userId);
        plan.setDestination(destination);
        plan.setPlanContent(content);
        plan.setCreatedAt(LocalDateTime.now());
        return repository.save(plan);
    }
    
    public List<TravelPlan> getUserPlans(String userId) {
        return repository.findByUserId(userId);
    }
}

6. REST API控制器

java 复制代码
@RestController
@RequestMapping("/api/travel")
public class TravelController {
    
    private final TravelAgentService travelAgentService;
    private final TravelPlanService travelPlanService;
    
    public TravelController(TravelAgentService travelAgentService,
                          TravelPlanService travelPlanService) {
        this.travelAgentService = travelAgentService;
        this.travelPlanService = travelPlanService;
    }
    
    @PostMapping("/plan")
    public ResponseEntity<TravelPlanResponse> createTravelPlan(@RequestBody TravelPlanRequest request) {
        String aiResponse = travelAgentService.planTravel(request.getUserRequest());
        
        // 保存旅行计划
        TravelPlan savedPlan = travelPlanService.savePlan(
            request.getUserId(), 
            request.getDestination(), 
            aiResponse
        );
        
        return ResponseEntity.ok(new TravelPlanResponse(savedPlan.getId(), aiResponse));
    }
    
    @GetMapping("/plan/stream")
    public ResponseEntity<StreamingResponseBody> createTravelPlanStream(
            @RequestParam String userRequest) {
        
        Flux<String> responseStream = travelAgentService.planTravelStream(userRequest);
        
        StreamingResponseBody streamingBody = outputStream -> {
            responseStream.doOnNext(chunk -> {
                try {
                    outputStream.write(("data: " + chunk + "\n\n").getBytes());
                    outputStream.flush();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }).blockLast();
        };
        
        return ResponseEntity.ok()
            .header("Content-Type", "text/plain; charset=utf-8")
            .body(streamingBody);
    }
    
    @GetMapping("/plans/{userId}")
    public ResponseEntity<List<TravelPlan>> getUserPlans(@PathVariable String userId) {
        return ResponseEntity.ok(travelPlanService.getUserPlans(userId));
    }
}

// DTO类
@Data
class TravelPlanRequest {
    private String userId;
    private String destination;
    private String userRequest;
}

@Data
@AllArgsConstructor
class TravelPlanResponse {
    private Long planId;
    private String content;
}

7. 使用示例

启动应用并测试

bash 复制代码
# 1. 设置环境变量
export OPENAI_API_KEY=your_openai_api_key

# 2. 启动应用
mvn spring-boot:run

# 3. 测试API
curl -X POST http://localhost:8080/api/travel/plan \
-H "Content-Type: application/json" \
-d '{
  "userId": "user123",
  "destination": "北京",
  "userRequest": "我想去北京旅游3天,请帮我制定详细的旅行计划,包括天气情况和景点推荐"
}'

预期AI响应示例

json 复制代码
{
  "planId": 1,
  "content": "根据您的需求,我为您制定了北京3天旅行计划:\n\n**天气情况**:\n北京当前天气:25°C,晴朗,湿度45%,风速15km/h,非常适合旅游。\n\n**推荐景点**:\n1. 故宫博物院 - 明清两代皇宫,世界文化遗产(评分4.8,门票60元)\n2. 天安门广场 - 世界最大城市广场之一(评分4.7,免费参观)\n3. 长城 - 世界七大奇迹之一(评分4.9,门票45元)\n\n**3天行程安排**:\n第一天:上午游览天安门广场,下午参观故宫博物院\n第二天:长城一日游\n第三天:探索北京胡同文化,品尝地道美食\n\n建议携带舒适的步行鞋和防晒用品。祝您旅途愉快!"
}

8. 高级功能扩展

添加缓存支持

java 复制代码
@Service
@EnableCaching
public class CachedTravelAgentService {
    
    private final TravelAgentService travelAgentService;
    
    @Cacheable(value = "travelPlans", key = "#userRequest")
    public String getCachedTravelPlan(String userRequest) {
        return travelAgentService.planTravel(userRequest);
    }
}

添加异步处理

java 复制代码
@Service
@Async
public class AsyncTravelService {
    
    @Async
    public CompletableFuture<String> generateTravelPlanAsync(String userRequest) {
        // 异步处理旅行计划生成
        return CompletableFuture.completedFuture("异步生成的旅行计划");
    }
}

这个完整的案例展示了:

  1. Function Calling: 通过定义天气、景点、距离查询函数,让AI能够调用外部服务
  2. LLM集成: 使用Spring AI的ChatClient与OpenAI GPT模型交互
  3. AI Agent: 创建智能旅行助手,能够理解用户意图并主动调用相关功能
  4. 实际应用: 包含完整的Web API、数据持久化和流式响应

该系统可以根据用户需求自动规划旅行,体现了现代AI应用的实用价值。

相关推荐
三声三视4 小时前
uni-app 鸿蒙端传参变成 [object Object]?顺着源码追到 ArkTS router 底层才搞明白
人工智能·ai·uni-app·aigc·ai编程·harmonyos
Java内核笔记9 小时前
告别十亿美元的错误 : Spring Boot 4 空安全 (JSpecify) 实战
java·spring boot·后端
DisonTangor9 小时前
【微软开源】Mage-Flow 深度解读:微软4B参数图像生成与编辑模型,单卡A100仅需0.59秒
人工智能·microsoft·ai作画·开源·aigc
码栈研说10 小时前
Go 语言大白话入门 10 - 排序与常用数据操作
后端·程序员
小阿鑫11 小时前
一个 AI 应用开发程序员的一天,都在屏幕前忙些什么?
ai·程序员·agent·rd270q·明基rd270q
海棠Flower未眠11 小时前
SpringBoot 消息死信队列(荣耀典藏版)
java·数据库·spring boot
腾渊信息科技公司12 小时前
Spring Boot + TDengine:工业视觉检测数据实时同步方案实战
spring boot·后端·tdengine
大模型搬砖师12 小时前
金融、制造、互联网:三个行业的 AI 网关落地实录
aigc·ai编程·gpu算力
猿的天空12 小时前
机器人双手迎来全栈训练系统:灵初智能EgoSteer让灵巧手无所不能
网络·人工智能·计算机·ai·程序员·机器人·编程
触底反弹12 小时前
🔥 AI 写代码总翻车?这套实战方法论救了我
人工智能·面试·程序员