|
|
@@ -1,18 +1,18 @@
|
|
|
# 第十三章 智能旅行助手
|
|
|
|
|
|
-在前面的章节中,我们从零开始构建了HelloAgents框架,实现了多种智能体范式、工具系统、记忆机制、协议通信和性能评估等核心功能。从本章开始,我们将进入一个全新的阶段:**将所学知识融会贯通,构建完整的实用应用。**
|
|
|
+在前面的章节中,我们从零开始构建了HelloAgents框架,实现了多种智能体范式、工具系统、记忆机制、协议通信和性能评估等核心功能。从本章开始,我们将进入一个全新的阶段:<strong>将所学知识融会贯通,构建完整的实用应用。</strong>
|
|
|
|
|
|
还记得在第一章中,我们构建的第一个智能体吗?那是一个简单的智能旅行助手,展示了`Thought-Action-Observation`循环的基本原理。本章的智能旅行助手将是一个完整的项目,包含以下核心功能:
|
|
|
|
|
|
-**(1)智能行程规划**:用户输入目的地、日期、偏好等信息,系统自动生成包含景点、餐饮、酒店的完整行程计划。
|
|
|
+<strong>(1)智能行程规划</strong>:用户输入目的地、日期、偏好等信息,系统自动生成包含景点、餐饮、酒店的完整行程计划。
|
|
|
|
|
|
-**(2)地图可视化**:在地图上标注景点位置、绘制游览路线,让行程一目了然。
|
|
|
+<strong>(2)地图可视化</strong>:在地图上标注景点位置、绘制游览路线,让行程一目了然。
|
|
|
|
|
|
-**(3)预算计算**:自动计算门票、酒店、餐饮、交通费用,显示预算明细。
|
|
|
+<strong>(3)预算计算</strong>:自动计算门票、酒店、餐饮、交通费用,显示预算明细。
|
|
|
|
|
|
-**(4)行程编辑**:支持添加、删除、调整景点,实时更新地图。
|
|
|
+<strong>(4)行程编辑</strong>:支持添加、删除、调整景点,实时更新地图。
|
|
|
|
|
|
-**(5)导出功能**:支持导出为PDF或图片,方便保存和分享。
|
|
|
+<strong>(5)导出功能</strong>:支持导出为PDF或图片,方便保存和分享。
|
|
|
|
|
|
|
|
|
|
|
|
@@ -22,7 +22,7 @@
|
|
|
|
|
|
规划一次旅行是一件既令人兴奋又令人头疼的事情。你需要在网上搜索景点信息,对比不同的攻略,查看天气预报,预订酒店,计算预算,规划路线。这个过程可能需要花费几个小时甚至几天的时间。而且即使花了这么多时间,你也不确定规划的行程是否合理,是否遗漏了什么重要的景点,预算是否准确。
|
|
|
|
|
|
-传统的旅行规划方式有几个痛点。首先是**信息分散**。景点信息在旅游网站上,天气信息在天气网站上,酒店信息在预订网站上,你需要在多个网站之间切换,手动整合这些信息。其次是**缺少个性化**。大部分攻略都是通用的,不考虑你的个人偏好、预算限制、出行时间等因素。最后是**难以调整**。当你想修改行程时,可能需要重新规划整个行程,因为景点的顺序、时间安排、预算都是相互关联的。
|
|
|
+传统的旅行规划方式有几个痛点。首先是<strong>信息分散</strong>。景点信息在旅游网站上,天气信息在天气网站上,酒店信息在预订网站上,你需要在多个网站之间切换,手动整合这些信息。其次是<strong>缺少个性化</strong>。大部分攻略都是通用的,不考虑你的个人偏好、预算限制、出行时间等因素。最后是<strong>难以调整</strong>。当你想修改行程时,可能需要重新规划整个行程,因为景点的顺序、时间安排、预算都是相互关联的。
|
|
|
|
|
|
AI技术为解决这些问题提供了新的可能。想象一下,你只需要告诉系统"我想去北京玩3天,喜欢历史文化,预算中等",系统就能自动为你生成一个完整的行程计划,包括每天去哪些景点、在哪里吃饭、住哪个酒店、需要多少预算。而且这个计划是可以调整的,你可以删除不喜欢的景点,调整游览顺序,系统会自动更新地图和预算。
|
|
|
|
|
|
@@ -30,20 +30,20 @@ AI技术为解决这些问题提供了新的可能。想象一下,你只需要
|
|
|
|
|
|
### 13.1.2 技术架构概览
|
|
|
|
|
|
-系统采用经典的**前后端分离架构**,分为四个层次,如图13.1所示:
|
|
|
+系统采用经典的<strong>前后端分离架构</strong>,分为四个层次,如图13.1所示:
|
|
|
|
|
|
<div align="center">
|
|
|
<img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/13-figures/13-1.png" alt="" width="85%"/>
|
|
|
<p>图 13.1 智能旅行助手技术架构</p>
|
|
|
</div>
|
|
|
|
|
|
-**(1)前端层 (Vue3+TypeScript)**:负责用户交互和数据展示,包括表单输入、结果展示、地图可视化。
|
|
|
+<strong>(1)前端层 (Vue3+TypeScript)</strong>:负责用户交互和数据展示,包括表单输入、结果展示、地图可视化。
|
|
|
|
|
|
-**(2)后端层 (FastAPI)**:负责API路由、数据验证、业务逻辑。
|
|
|
+<strong>(2)后端层 (FastAPI)</strong>:负责API路由、数据验证、业务逻辑。
|
|
|
|
|
|
-**(3)智能体层 (HelloAgents)**:负责任务分解、工具调用、结果整合。包含4个专门的Agent。
|
|
|
+<strong>(3)智能体层 (HelloAgents)</strong>:负责任务分解、工具调用、结果整合。包含4个专门的Agent。
|
|
|
|
|
|
-**(4)外部服务层**:提供数据和能力,包括高德地图API、Unsplash API、OpenAI API。
|
|
|
+<strong>(4)外部服务层</strong>:提供数据和能力,包括高德地图API、Unsplash API、OpenAI API。
|
|
|
|
|
|
数据流转过程如下:用户在前端填写表单 → 后端验证数据 → 调用智能体系统 → 智能体依次调用景点搜索、天气查询、酒店推荐、行程规划Agent → 每个Agent通过MCP协议调用外部API → 整合结果返回前端 → 前端渲染展示。
|
|
|
|
|
|
@@ -74,13 +74,13 @@ helloagents-trip-planner/
|
|
|
|
|
|
在深入学习实现细节之前,让我们先把项目跑起来,看看最终的效果。这样你会对整个系统有一个直观的认识。
|
|
|
|
|
|
-**环境要求:**
|
|
|
+<strong>环境要求:</strong>
|
|
|
|
|
|
- Python 3.10或更高版本
|
|
|
- Node.js 16.0或更高版本
|
|
|
- npm 8.0或更高版本
|
|
|
|
|
|
-**获取API密钥:**
|
|
|
+<strong>获取API密钥:</strong>
|
|
|
|
|
|
你需要准备以下API密钥:
|
|
|
|
|
|
@@ -158,13 +158,13 @@ npm run dev
|
|
|
|
|
|
### 13.2.1 Web应用中的数据流转
|
|
|
|
|
|
-在构建智能旅行助手时,我们需要解决一个核心问题:**如何表示和传递旅行计划数据?**
|
|
|
+在构建智能旅行助手时,我们需要解决一个核心问题:<strong>如何表示和传递旅行计划数据?</strong>
|
|
|
|
|
|
我们需要理解一个完整的Web应用中数据是如何流转的。想象一下,当用户在浏览器中点击"开始规划"按钮时,会发生什么?
|
|
|
|
|
|
用户在前端填写的表单数据(目的地、日期、预算等)需要通过HTTP请求发送到后端服务器。后端接收到数据后,会调用智能体系统进行处理。智能体又会调用高德地图API、Unsplash API等外部服务获取数据。这些外部API返回的数据格式各不相同,有的用`lng`,有的用`lon`,有的用`longitude`。最后,后端需要将处理好的数据返回给前端,前端再渲染成用户看到的页面。
|
|
|
|
|
|
-在这个过程中,数据经历了多次转换:前端表单 → HTTP请求 → 后端Python对象 → 外部API响应 → 后端Python对象 → HTTP响应 → 前端TypeScript对象 → 页面展示。如果没有统一的数据格式,每一步转换都可能出错。这就是为什么我们需要**数据模型**。
|
|
|
+在这个过程中,数据经历了多次转换:前端表单 → HTTP请求 → 后端Python对象 → 外部API响应 → 后端Python对象 → HTTP响应 → 前端TypeScript对象 → 页面展示。如果没有统一的数据格式,每一步转换都可能出错。这就是为什么我们需要<strong>数据模型</strong>。
|
|
|
|
|
|
### 13.2.2 从字典到Pydantic模型
|
|
|
|
|
|
@@ -182,11 +182,11 @@ attraction = {
|
|
|
lng = attraction["location"]["lng"]
|
|
|
```
|
|
|
|
|
|
-这种方式在原型阶段很方便,但在实际项目中会遇到很多问题。首先是**字段名不统一**的问题。高德地图API返回的位置数据是`"116.397128,39.916527"`这样的字符串,需要手动分割成经纬度。而Unsplash API可能使用`longitude`和`latitude`。如果我们在代码中到处都用字典,就需要在每个地方都处理这些差异。
|
|
|
+这种方式在原型阶段很方便,但在实际项目中会遇到很多问题。首先是<strong>字段名不统一</strong>的问题。高德地图API返回的位置数据是`"116.397128,39.916527"`这样的字符串,需要手动分割成经纬度。而Unsplash API可能使用`longitude`和`latitude`。如果我们在代码中到处都用字典,就需要在每个地方都处理这些差异。
|
|
|
|
|
|
-其次是**类型安全**的问题。假设我们不小心把`price`写成了字符串`"60"`,在Python中这不会立即报错,但在计算总预算时就会出问题。更糟糕的是,这种错误只能在运行时才能发现,而且错误信息可能很难定位。
|
|
|
+其次是<strong>类型安全</strong>的问题。假设我们不小心把`price`写成了字符串`"60"`,在Python中这不会立即报错,但在计算总预算时就会出问题。更糟糕的是,这种错误只能在运行时才能发现,而且错误信息可能很难定位。
|
|
|
|
|
|
-最后是**维护性**的问题。当我们需要给景点添加新字段(比如`rating`评分)时,需要在代码的多个地方修改。如果遗漏了某个地方,就会导致数据不一致。
|
|
|
+最后是<strong>维护性</strong>的问题。当我们需要给景点添加新字段(比如`rating`评分)时,需要在代码的多个地方修改。如果遗漏了某个地方,就会导致数据不一致。
|
|
|
|
|
|
Pydantic提供了一个解决方案。它是Python的数据验证库,可以让我们用类来定义数据结构,并自动处理验证、转换和序列化。让我们看一个简单的例子:
|
|
|
|
|
|
@@ -241,7 +241,7 @@ class DayPlan(BaseModel):
|
|
|
hotel: Optional[Hotel] = None # 可选的酒店信息
|
|
|
```
|
|
|
|
|
|
-最强大的功能之一是**自定义验证器**。有时候外部API返回的数据格式不符合我们的要求,我们可以使用`field_validator`装饰器来自定义验证和转换逻辑。比如,高德地图返回的温度是`"16°C"`这样的字符串,我们需要把它转换成数字:
|
|
|
+最强大的功能之一是<strong>自定义验证器</strong>。有时候外部API返回的数据格式不符合我们的要求,我们可以使用`field_validator`装饰器来自定义验证和转换逻辑。比如,高德地图返回的温度是`"16°C"`这样的字符串,我们需要把它转换成数字:
|
|
|
|
|
|
```python
|
|
|
from pydantic import field_validator
|
|
|
@@ -262,9 +262,9 @@ class WeatherInfo(BaseModel):
|
|
|
|
|
|
### 13.2.4 自底向上的模型设计
|
|
|
|
|
|
-现在让我们开始设计智能旅行助手的数据模型。一个好的设计原则是**自底向上**:先定义最基础的模型,然后逐步组合成复杂的结构。这样做的好处是每个模型都很简单,容易理解和维护。
|
|
|
+现在让我们开始设计智能旅行助手的数据模型。一个好的设计原则是<strong>自底向上</strong>:先定义最基础的模型,然后逐步组合成复杂的结构。这样做的好处是每个模型都很简单,容易理解和维护。
|
|
|
|
|
|
-最基础的模型是**位置信息**。无论是景点、酒店还是餐厅,都需要位置信息。我们定义一个`Location`类来表示经纬度坐标:
|
|
|
+最基础的模型是<strong>位置信息</strong>。无论是景点、酒店还是餐厅,都需要位置信息。我们定义一个`Location`类来表示经纬度坐标:
|
|
|
|
|
|
```python
|
|
|
class Location(BaseModel):
|
|
|
@@ -275,7 +275,7 @@ class Location(BaseModel):
|
|
|
|
|
|
这里我们使用了范围验证(`ge`表示大于等于,`le`表示小于等于),确保经纬度的值在合理范围内。
|
|
|
|
|
|
-接下来是**景点信息**。一个景点包含名称、地址、位置、游览时间、描述、评分、图片和门票价格等信息。注意我们使用了`Location`作为字段类型,这就是嵌套模型:
|
|
|
+接下来是<strong>景点信息</strong>。一个景点包含名称、地址、位置、游览时间、描述、评分、图片和门票价格等信息。注意我们使用了`Location`作为字段类型,这就是嵌套模型:
|
|
|
|
|
|
```python
|
|
|
class Attraction(BaseModel):
|
|
|
@@ -291,7 +291,7 @@ class Attraction(BaseModel):
|
|
|
ticket_price: int = Field(default=0,ge=0,description="门票价格(元)")
|
|
|
```
|
|
|
|
|
|
-类似地,我们定义**餐饮信息**和**酒店信息**。这些模型的结构都很相似,都包含名称、地址、位置和费用等基本信息:
|
|
|
+类似地,我们定义<strong>餐饮信息</strong>和<strong>酒店信息</strong>。这些模型的结构都很相似,都包含名称、地址、位置和费用等基本信息:
|
|
|
|
|
|
```python
|
|
|
class Meal(BaseModel):
|
|
|
@@ -315,7 +315,7 @@ class Hotel(BaseModel):
|
|
|
estimated_cost: int = Field(default=0,description="预估费用(元/晚)")
|
|
|
```
|
|
|
|
|
|
-**预算信息**是一个特殊的模型,它不包含位置信息,而是包含各项费用的汇总:
|
|
|
+<strong>预算信息</strong>是一个特殊的模型,它不包含位置信息,而是包含各项费用的汇总:
|
|
|
|
|
|
```python
|
|
|
class Budget(BaseModel):
|
|
|
@@ -327,7 +327,7 @@ class Budget(BaseModel):
|
|
|
total: int = Field(default=0,description="总费用")
|
|
|
```
|
|
|
|
|
|
-现在我们可以组合这些基础模型,构建**单日行程**。一个单日行程包含日期、描述、交通方式、住宿安排、酒店、景点列表和餐饮列表:
|
|
|
+现在我们可以组合这些基础模型,构建<strong>单日行程</strong>。一个单日行程包含日期、描述、交通方式、住宿安排、酒店、景点列表和餐饮列表:
|
|
|
|
|
|
```python
|
|
|
class DayPlan(BaseModel):
|
|
|
@@ -344,7 +344,7 @@ class DayPlan(BaseModel):
|
|
|
|
|
|
注意这里使用了`List[Attraction]`来表示景点列表,`default_factory=list`表示默认值是一个空列表。
|
|
|
|
|
|
-**天气信息**需要特殊处理,因为高德地图返回的温度格式不规范。我们使用自定义验证器来处理:
|
|
|
+<strong>天气信息</strong>需要特殊处理,因为高德地图返回的温度格式不规范。我们使用自定义验证器来处理:
|
|
|
|
|
|
```python
|
|
|
class WeatherInfo(BaseModel):
|
|
|
@@ -369,7 +369,7 @@ class WeatherInfo(BaseModel):
|
|
|
return v
|
|
|
```
|
|
|
|
|
|
-最后,我们定义**完整的旅行计划**。这是最顶层的模型,包含了所有的信息:
|
|
|
+最后,我们定义<strong>完整的旅行计划</strong>。这是最顶层的模型,包含了所有的信息:
|
|
|
|
|
|
```python
|
|
|
class TripPlan(BaseModel):
|
|
|
@@ -445,11 +445,11 @@ interface TripPlan {
|
|
|
|
|
|
如果用单个Agent来完成旅行规划。这个Agent需要做什么呢?首先,它要搜索景点信息,这需要调用高德地图的POI搜索工具。然后,它要查询天气信息,这需要调用天气查询工具。接着,它要搜索酒店信息,这又需要调用POI搜索工具。最后,它要把所有这些信息整合起来,生成一个完整的旅行计划。
|
|
|
|
|
|
-这听起来很简单,但实际操作时会遇到第一个问题:**工具调用的限制**。SimpleAgent每次`run()`调用只能执行一个工具。这意味着我们需要多次调用`run()`方法,每次调用处理一个任务。但这样做会带来一个新问题:如何在多次调用之间传递信息?第一次调用得到的景点信息,如何传递给第二次调用?我们需要手动管理这些中间结果,代码会变得很复杂。
|
|
|
+这听起来很简单,但实际操作时会遇到第一个问题:<strong>工具调用的限制</strong>。SimpleAgent每次`run()`调用只能执行一个工具。这意味着我们需要多次调用`run()`方法,每次调用处理一个任务。但这样做会带来一个新问题:如何在多次调用之间传递信息?第一次调用得到的景点信息,如何传递给第二次调用?我们需要手动管理这些中间结果,代码会变得很复杂。
|
|
|
|
|
|
-当然,我们可以使用ReactAgent来解决这个问题。ReactAgent可以在一次调用中执行多个工具,它会自动进行多轮思考和行动。但这又带来了新的问题:**时间成本**。ReactAgent的每一轮思考都需要调用LLM,如果需要调用三个工具,就需要至少三轮思考,这意味着至少三次LLM调用。而且这些调用是串行的,必须等前一个完成才能开始下一个,总时间会很长。
|
|
|
+当然,我们可以使用ReactAgent来解决这个问题。ReactAgent可以在一次调用中执行多个工具,它会自动进行多轮思考和行动。但这又带来了新的问题:<strong>时间成本</strong>。ReactAgent的每一轮思考都需要调用LLM,如果需要调用三个工具,就需要至少三轮思考,这意味着至少三次LLM调用。而且这些调用是串行的,必须等前一个完成才能开始下一个,总时间会很长。
|
|
|
|
|
|
-第二个问题是**提示词的复杂度**。如果我们要让一个Agent完成所有任务,就需要在提示词中详细描述每个任务的执行逻辑。比如:
|
|
|
+第二个问题是<strong>提示词的复杂度</strong>。如果我们要让一个Agent完成所有任务,就需要在提示词中详细描述每个任务的执行逻辑。比如:
|
|
|
|
|
|
```python
|
|
|
COMPLEX_PROMPT = """你是旅行规划助手。你需要:
|
|
|
@@ -461,7 +461,7 @@ COMPLEX_PROMPT = """你是旅行规划助手。你需要:
|
|
|
"""
|
|
|
```
|
|
|
|
|
|
-这样的提示词有几个问题。首先是**难以维护**。如果我们想修改景点搜索的逻辑(比如增加评分筛选),就需要修改整个提示词,很容易影响到其他部分。其次是**容易出错**。LLM需要同时理解多个任务的要求,很容易搞混不同任务的格式和参数。最后是**难以调试**。当生成的计划不符合预期时,我们很难知道是哪个环节出了问题,是景点搜索不准确,还是天气查询失败,还是整合逻辑有问题?
|
|
|
+这样的提示词有几个问题。首先是<strong>难以维护</strong>。如果我们想修改景点搜索的逻辑(比如增加评分筛选),就需要修改整个提示词,很容易影响到其他部分。其次是<strong>容易出错</strong>。LLM需要同时理解多个任务的要求,很容易搞混不同任务的格式和参数。最后是<strong>难以调试</strong>。当生成的计划不符合预期时,我们很难知道是哪个环节出了问题,是景点搜索不准确,还是天气查询失败,还是整合逻辑有问题?
|
|
|
|
|
|
面对这些问题,一个自然的想法是:能不能把复杂的任务分解成多个简单的任务,让不同的Agent各司其职?这就是多Agent协作的核心思想。
|
|
|
|
|
|
@@ -476,17 +476,17 @@ COMPLEX_PROMPT = """你是旅行规划助手。你需要:
|
|
|
<p>图 13.6 多智能体协作流程</p>
|
|
|
</div>
|
|
|
|
|
|
-- **AttractionSearchAgent(景点搜索专家)**专注于搜索景点信息。它只需要理解用户的偏好(比如"历史文化"、"自然风光"),然后调用高德地图的POI搜索工具,返回相关的景点列表。它的提示词很简单,只需要说明如何根据偏好选择关键词,如何调用工具。
|
|
|
+- <strong>AttractionSearchAgent(景点搜索专家)</strong>专注于搜索景点信息。它只需要理解用户的偏好(比如"历史文化"、"自然风光"),然后调用高德地图的POI搜索工具,返回相关的景点列表。它的提示词很简单,只需要说明如何根据偏好选择关键词,如何调用工具。
|
|
|
|
|
|
-- **WeatherQueryAgent(天气查询专家)**专注于查询天气信息。它只需要知道城市名称,然后调用天气查询工具,返回未来几天的天气预报。它的任务非常明确,几乎不会出错。
|
|
|
+- <strong>WeatherQueryAgent(天气查询专家)</strong>专注于查询天气信息。它只需要知道城市名称,然后调用天气查询工具,返回未来几天的天气预报。它的任务非常明确,几乎不会出错。
|
|
|
|
|
|
-- **HotelAgent(酒店推荐专家)**专注于搜索酒店信息。它需要理解用户的住宿需求(比如"经济型"、"豪华型"),然后调用POI搜索工具,返回符合要求的酒店列表。
|
|
|
+- <strong>HotelAgent(酒店推荐专家)</strong>专注于搜索酒店信息。它需要理解用户的住宿需求(比如"经济型"、"豪华型"),然后调用POI搜索工具,返回符合要求的酒店列表。
|
|
|
|
|
|
-- **PlannerAgent(行程规划专家)**负责整合所有信息。它接收前三个Agent的输出,加上用户的原始需求(日期、预算等),然后生成完整的旅行计划。它不需要调用任何外部工具,只需要专注于信息的整合和行程的安排。
|
|
|
+- <strong>PlannerAgent(行程规划专家)</strong>负责整合所有信息。它接收前三个Agent的输出,加上用户的原始需求(日期、预算等),然后生成完整的旅行计划。它不需要调用任何外部工具,只需要专注于信息的整合和行程的安排。
|
|
|
|
|
|
现在让我们详细设计每个Agent的角色和提示词。设计提示词时,我们需要考虑几个关键问题:这个Agent需要什么输入?它应该产生什么输出?它需要调用什么工具?它可能遇到什么问题?
|
|
|
|
|
|
-**AttractionSearchAgent**的任务是根据用户偏好搜索景点。它的输入是城市名称和用户偏好(比如"历史文化"、"自然风光")。它需要调用`amap_maps_text_search`工具,参数是关键词和城市。它的输出是景点列表,包含名称、地址、评分等信息。
|
|
|
+<strong>AttractionSearchAgent</strong>的任务是根据用户偏好搜索景点。它的输入是城市名称和用户偏好(比如"历史文化"、"自然风光")。它需要调用`amap_maps_text_search`工具,参数是关键词和城市。它的输出是景点列表,包含名称、地址、评分等信息。
|
|
|
|
|
|
```python
|
|
|
ATTRACTION_AGENT_PROMPT = """你是景点搜索专家。
|
|
|
@@ -506,7 +506,7 @@ ATTRACTION_AGENT_PROMPT = """你是景点搜索专家。
|
|
|
|
|
|
这个提示词很简洁,但包含了所有必要的信息。它明确说明了工具调用的格式,提供了具体的示例,还强调了两个重要原则:必须使用工具(不能编造),要根据用户偏好搜索。
|
|
|
|
|
|
-**WeatherQueryAgent**的任务更简单,只需要查询天气。它的输入是城市名称,输出是天气信息。
|
|
|
+<strong>WeatherQueryAgent</strong>的任务更简单,只需要查询天气。它的输入是城市名称,输出是天气信息。
|
|
|
|
|
|
```python
|
|
|
WEATHER_AGENT_PROMPT = """你是天气查询专家。
|
|
|
@@ -518,7 +518,7 @@ WEATHER_AGENT_PROMPT = """你是天气查询专家。
|
|
|
"""
|
|
|
```
|
|
|
|
|
|
-**HotelAgent**的任务是搜索酒店。它的输入是城市名称和住宿类型,输出是酒店列表。
|
|
|
+<strong>HotelAgent</strong>的任务是搜索酒店。它的输入是城市名称和住宿类型,输出是酒店列表。
|
|
|
|
|
|
```python
|
|
|
HOTEL_AGENT_PROMPT = """你是酒店推荐专家。
|
|
|
@@ -530,7 +530,7 @@ HOTEL_AGENT_PROMPT = """你是酒店推荐专家。
|
|
|
"""
|
|
|
```
|
|
|
|
|
|
-**PlannerAgent**是最复杂的,因为它需要整合所有信息。它的输入是用户需求和前三个Agent的输出,输出是完整的旅行计划(JSON格式)。
|
|
|
+<strong>PlannerAgent</strong>是最复杂的,因为它需要整合所有信息。它的输入是用户需求和前三个Agent的输出,输出是完整的旅行计划(JSON格式)。
|
|
|
|
|
|
```python
|
|
|
PLANNER_AGENT_PROMPT = """你是行程规划专家。
|
|
|
@@ -664,13 +664,13 @@ def search_poi(keywords: str,city: str,api_key: str):
|
|
|
return data
|
|
|
```
|
|
|
|
|
|
-这种方式看起来很简单,但在实际使用中会遇到几个问题。首先是**Agent无法自主调用**。在我们的HelloAgents框架中,Agent通过识别提示词中的工具调用标记(比如`[TOOL_CALL:tool_name:arg1=value1]`)来调用工具。如果我们直接在代码中调用API,Agent就失去了自主决策的能力,变成了一个简单的函数调用。
|
|
|
+这种方式看起来很简单,但在实际使用中会遇到几个问题。首先是<strong>Agent无法自主调用</strong>。在我们的HelloAgents框架中,Agent通过识别提示词中的工具调用标记(比如`[TOOL_CALL:tool_name:arg1=value1]`)来调用工具。如果我们直接在代码中调用API,Agent就失去了自主决策的能力,变成了一个简单的函数调用。
|
|
|
|
|
|
-其次是**参数传递复杂**。高德地图的API有很多参数,比如POI搜索有`keywords`、`city`、`types`、`offset`、`page`等十几个参数。如果我们要让Agent能够灵活使用这些参数,就需要在提示词中详细说明每个参数的含义和格式,这会让提示词变得非常复杂。
|
|
|
+其次是<strong>参数传递复杂</strong>。高德地图的API有很多参数,比如POI搜索有`keywords`、`city`、`types`、`offset`、`page`等十几个参数。如果我们要让Agent能够灵活使用这些参数,就需要在提示词中详细说明每个参数的含义和格式,这会让提示词变得非常复杂。
|
|
|
|
|
|
-第三是**响应解析困难**。高德地图API返回的是JSON格式的数据,结构比较复杂。我们需要编写代码来解析这些数据,提取我们需要的字段。如果API的响应格式发生变化,我们就需要修改解析代码。
|
|
|
+第三是<strong>响应解析困难</strong>。高德地图API返回的是JSON格式的数据,结构比较复杂。我们需要编写代码来解析这些数据,提取我们需要的字段。如果API的响应格式发生变化,我们就需要修改解析代码。
|
|
|
|
|
|
-最后是**工具管理混乱**。高德地图提供了十几个不同的API(POI搜索、天气查询、路线规划等),如果我们为每个API都编写一个函数,然后手动注册到Agent的工具列表中,代码会变得很冗长。而且当我们想添加新的API时,需要修改多个地方。
|
|
|
+最后是<strong>工具管理混乱</strong>。高德地图提供了十几个不同的API(POI搜索、天气查询、路线规划等),如果我们为每个API都编写一个函数,然后手动注册到Agent的工具列表中,代码会变得很冗长。而且当我们想添加新的API时,需要修改多个地方。
|
|
|
|
|
|
### 13.4.2 高德地图MCP集成
|
|
|
|
|
|
@@ -909,7 +909,7 @@ async def create_trip_plan(request: TripPlanRequest) -> TripPlan:
|
|
|
|
|
|
在开始前端开发之前,我们需要理解现代Web应用的架构模式。在早期的Web开发中,前端和后端是混在一起的,比如PHP、JSP这样的技术,HTML模板和业务逻辑代码写在同一个文件里。这种方式在小项目中很方便,但在大型项目中会遇到很多问题:前端和后端开发者需要频繁协调,代码难以复用,测试困难。
|
|
|
|
|
|
-现代Web应用普遍采用**前后端分离**的架构。后端只负责提供API接口,返回JSON格式的数据。前端是一个独立的应用,通过HTTP请求调用后端API,获取数据后渲染页面。这种架构有几个明显的优势:前端和后端可以独立开发、独立部署、独立测试;前端可以是Web应用、移动应用或桌面应用,都使用同一套后端API;前端可以使用现代的框架和工具链,提供更好的用户体验。
|
|
|
+现代Web应用普遍采用<strong>前后端分离</strong>的架构。后端只负责提供API接口,返回JSON格式的数据。前端是一个独立的应用,通过HTTP请求调用后端API,获取数据后渲染页面。这种架构有几个明显的优势:前端和后端可以独立开发、独立部署、独立测试;前端可以是Web应用、移动应用或桌面应用,都使用同一套后端API;前端可以使用现代的框架和工具链,提供更好的用户体验。
|
|
|
|
|
|
在我们的智能旅行助手项目中,后端是用Python和FastAPI实现的,提供了一个核心API接口`POST /api/trip/plan`,接收旅行需求,返回旅行计划。前端是用Vue 3和TypeScript实现的,是一个单页应用(SPA),用户在浏览器中填写表单,点击"开始规划"按钮,前端发送HTTP请求到后端,等待响应,然后渲染结果页面。整个过程中,页面不会刷新,用户体验很流畅。
|
|
|
|
|
|
@@ -1353,7 +1353,7 @@ const handleSubmit = async () => {
|
|
|
|
|
|
AI生成的旅行计划虽然很智能,但可能不完全符合用户的个人需求。比如,用户可能不喜欢某个景点,想删除它;或者想调整景点的游览顺序。我们提供了行程编辑功能,让用户可以自定义行程。
|
|
|
|
|
|
-编辑功能的核心是**状态管理**。我们需要维护两个状态:当前的行程计划和原始的行程计划。当用户进入编辑模式时,我们保存原始计划的副本。如果用户取消编辑,就恢复原始计划。如果用户保存修改,就更新当前计划:
|
|
|
+编辑功能的核心是<strong>状态管理</strong>。我们需要维护两个状态:当前的行程计划和原始的行程计划。当用户进入编辑模式时,我们保存原始计划的副本。如果用户取消编辑,就恢复原始计划。如果用户保存修改,就更新当前计划:
|
|
|
|
|
|
```typescript
|
|
|
const editMode = ref(false)
|
|
|
@@ -1431,10 +1431,10 @@ const cancelEdit = () => {
|
|
|
|
|
|
我们尝试了多种解决方案,包括将地图Canvas转换成图片后再导出,但由于高德地图的Canvas渲染机制和跨域限制,这个方案并没有完全解决问题。在实际项目中,可能需要考虑以下替代方案:
|
|
|
|
|
|
-1. **使用高德地图的静态地图API**:调用`maps_staticmap`工具生成静态地图图片,替代动态地图
|
|
|
-2. **分开导出**:地图和行程内容分开导出,最后在后端合并
|
|
|
-3. **使用截图服务**:使用Puppeteer等无头浏览器在服务端截图
|
|
|
-4. **简化导出内容**:导出时隐藏地图,只导出文字内容
|
|
|
+1. <strong>使用高德地图的静态地图API</strong>:调用`maps_staticmap`工具生成静态地图图片,替代动态地图
|
|
|
+2. <strong>分开导出</strong>:地图和行程内容分开导出,最后在后端合并
|
|
|
+3. <strong>使用截图服务</strong>:使用Puppeteer等无头浏览器在服务端截图
|
|
|
+4. <strong>简化导出内容</strong>:导出时隐藏地图,只导出文字内容
|
|
|
|
|
|
目前的实现中,我们采用了简化方案,在导出时暂时隐藏地图部分,只导出行程的文字内容和景点信息。虽然这不是最理想的方案,但可以保证导出功能的可用性。
|
|
|
|
|
|
@@ -1561,10 +1561,10 @@ const scrollToSection = ({ key }: { key: string }) => {
|
|
|
|
|
|
通过本章,你不仅学会了如何构建一个完整的智能旅行助手应用,更重要的是掌握了:
|
|
|
|
|
|
-1. **系统设计思维**: 如何将复杂问题分解为多个简单任务
|
|
|
-2. **工程实践能力**: 如何将理论知识转化为可运行的代码
|
|
|
-3. **全栈开发能力**: 如何整合前后端技术栈
|
|
|
-4. **AI应用开发**: 如何利用LLM构建实用的应用
|
|
|
+1. <strong>系统设计思维</strong>: 如何将复杂问题分解为多个简单任务
|
|
|
+2. <strong>工程实践能力</strong>: 如何将理论知识转化为可运行的代码
|
|
|
+3. <strong>全栈开发能力</strong>: 如何整合前后端技术栈
|
|
|
+4. <strong>AI应用开发</strong>: 如何利用LLM构建实用的应用
|
|
|
|
|
|
这个项目是一个起点,而不是终点。你可以基于这个项目:
|
|
|
|