创建一个基于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应用的实用价值。

相关推荐
昵称为空C15 小时前
SpringBoot3 http接口调用新方式RestClient + @HttpExchange像使用Feign一样调用
spring boot·后端
唐某人丶16 小时前
教你如何用 JS 实现 Agent 系统(2)—— 开发 ReAct 版本的“深度搜索”
前端·人工智能·aigc
袁庭新16 小时前
全球首位AI机器人部长,背负反腐重任
人工智能·aigc
大模型教程17 小时前
8GB显存笔记本能跑多大AI模型?这个计算公式90%的人都不知道!
程序员·llm·agent
大模型教程17 小时前
大模型应用开发到底有多赚钱?看完这5个真实案例,你会惊掉下巴
程序员·llm·agent
算家计算17 小时前
AI配音革命!B站最新开源IndexTTS2本地部署教程:精准对口型,情感随心换
人工智能·开源·aigc
AI大模型17 小时前
别乱装!Ollama×DeepSeek×AnythingLLM一键本地AI知识库,快人10倍
程序员·llm·agent
拳打南山敬老院18 小时前
漫谈 MCP 构建之概念篇
前端·后端·aigc
舒一笑20 小时前
Saga分布式事务框架执行逻辑
后端·程序员·设计
xiezhr21 小时前
近期提高幸福感的工具分享
程序员