创建一个基于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 分钟前
Coze智能体实战:3分钟构建专属数字人!公众号文章一键转为数字人口播视频(附喂饭级教程)
人工智能·aigc·coze
Q_Q5110082857 分钟前
python+django/flask+uniapp宠物中心信息管理系统app
spring boot·python·django·flask·uni-app·node.js·php
风象南22 分钟前
SpringBoot的4种Bean注入冲突解决方案
java·spring boot·后端
马可奥勒留1 小时前
我想吐槽一下稀土掘金
程序员
迢迢星万里灬1 小时前
Java求职者面试题解析:Spring、Spring Boot、MyBatis框架与源码原理
java·spring boot·spring·mybatis·面试题
键盘歌唱家2 小时前
AIGC方案-java实现视频伪动效果
java·aigc·音视频
weixin_985432119 小时前
Spring Boot 中的 @ConditionalOnBean 注解详解
java·spring boot·后端
Java知识库11 小时前
2025秋招后端突围:JVM核心面试题与高频考点深度解析
java·jvm·程序员·java面试·后端开发
墨风如雪11 小时前
告别低效!Claude Code:你的代码库来了个“全能管家”
aigc