Mapper|Map-Interactive Travel Agent(demo)
2025-10-03
· Junyi Yan、Claude Sonnet 4.5
本项目受 MapScroll 启发。
地图是数字化时代重要的基础设施。Agent时代,无论作为Tools还是通过MCP调用,在各类智能场景下的地图数据与使用交互,成为了产品设计新的思考命题。
项目概述
「Mapper」是一款基于 AI Agent 自主规划能力、且地图交互联动的智能旅游规划系统,此 demo 聚焦在川西旅游规划。通过先进的 AI 技术和地图交互,为用户提供个性化、智能化的旅行规划服务。
- AI 自主决策 - Agent 自动识别意图、提取景点、规划路线
- 实时地图交互 - 双向联动,所见即所得
- 极简设计 - 专注核心体验,75% 地图 + 25% 对话
- 流式响应 - 思考过程可视化,实时反馈
- 多模式切换 - 单点查询 / 行程规划 / 路线导航

技术架构
技术栈
前端
框架: Next.js 15 (App Router)
语言: TypeScript
状态管理: Zustand
UI 组件: Tailwind CSS
地图引擎: 高德地图 2.0 (@amap/amap-jsapi-loader)
实时通信: Socket.io-client
Markdown 渲染: react-markdown后端
运行时: Node.js 20+ / Bun
框架: Fastify
语言: TypeScript
AI 引擎: Claude Sonnet 4.5 / DeepSeek
实时通信: Socket.io
地图服务: 高德地图 API
日志: Pino架构设计
┌─────────────────────────────────────────────────────────┐
│ 前端 (Next.js) │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ MapContainer │◄─────►│ PlanningPanel │ │
│ │ (地图 75%) │ 双向 │ (对话 25%) │ │
│ │ - 高德地图 │ 联动 │ - 流式对话 │ │
│ │ - 标记/路线 │ │ - 思考过程 │ │
│ └─────────────────┘ └──────────────────────┘ │
└────────────────────┬─────────────────────────────────────┘
│ WebSocket (Socket.io)
↓
┌─────────────────────────────────────────────────────────┐
│ 后端 Agent 引擎 (Fastify) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ PlanningAgent (核心 AI 编排) │ │
│ │ - 意图识别 (单点/多点/路线) │ │
│ │ - 景点提取 (LLM + 正则) │ │
│ │ - 实时流式响应 │ │
│ │ - 思考步骤发送 │ │
│ └──────────────────┬───────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 工具层 (Tools) │ │
│ │ - AmapSearchTool (POI 搜索 + 地理过滤) │ │
│ │ - RoutePlanningTool (路线规划) │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘工作流程
用户输入
↓
PlanningAgent.generateStreamResponse()
↓
├─→ LLM 生成回复(流式) ──→ 前端显示
↓
└─→ extractAttractions() 提取景点
↓
AmapSearchTool.batchSearch() 搜索坐标
↓
WebSocket: map:addMarkers ──→ 前端地图添加标记
↓
detectRouteIntent() 检测路线意图
↓
RoutePlanningTool.planRoute() 规划路线
↓
WebSocket: map:drawRoute ──→ 前端地图绘制路线核心功能一:Agent 自主规划
功能描述
AI Agent 能够理解用户的自然语言输入,自主分析意图并生成执行计划,无需预定义规则即可完成复杂的多步骤任务。
核心实现
1. 意图分析与任务规划
文件:backend/src/agent/PlanningAgent.ts
// 核心方法:plan() - 分析用户意图并生成任务计划
async plan(userInput: string, context: ExecutionContext): Promise<Task[]> {
// 构建规划提示词
const prompt = buildPlanningPrompt(userInput, context);
const systemPrompt = PLANNING_SYSTEM_PROMPT;
// 调用 LLM(Claude/DeepSeek)生成任务计划
const response = await this.anthropicClient.messages.create({
model: this.model,
max_tokens: 2000,
temperature: 0.7,
system: systemPrompt,
messages: [{ role: 'user', content: prompt }],
});
// 提取并解析 JSON 格式的任务列表
const planJson = this.extractJSON(content);
return this.parseTasks(planJson);
}
关键点:
- 使用 LLM 进行意图理解和任务分解
- 返回结构化的任务列表(Task[]),每个任务包含类型、描述、参数、依赖关系等
- 支持 Claude 和 DeepSeek 两种 LLM 提供商
2. 任务执行引擎
文件:backend/src/agent/ExecutionEngine.ts
// 核心方法:execute() - 按依赖关系执行任务
async execute(tasks: Task[], context: ExecutionContext): Promise<ExecutionResult> {
const results = new Map<string, ToolResult>();
// 按优先级排序任务
const sortedTasks = [...tasks].sort((a, b) => a.priority - b.priority);
for (const task of sortedTasks) {
// 检查依赖是否满足
if (!this.checkDependencies(task, results)) {
throw new Error(`Dependencies not met for task: ${task.id}`);
}
// 发送思考步骤到前端
context.websocket.emit('thinking:step', {
taskId: task.id,
content: task.description,
});
// 获取对应工具并执行
const tool = this.getToolForTask(task);
const result = await tool.execute(task.params, context);
results.set(task.id, result);
}
return { success: true, results, completedTasks, failedTasks };
}
关键点:
- 依赖检查:确保任务按正确顺序执行
- 工具映射:根据任务类型选择合适的工具
- 实时反馈:通过 WebSocket 向前端发送执行状态
3. 智能景点提取
文件:backend/src/agent/PlanningAgent.ts:199-241
// 从 AI 回复中自动提取景点名称
private async extractAttractions(text: string): Promise<string[]> {
const systemPrompt = ATTRACTION_EXTRACTION_PROMPT;
// 使用专门的提示词让 LLM 提取景点
const response = await this.anthropicClient.messages.create({
model: this.model,
max_tokens: 500,
temperature: 0.1, // 低温度确保准确性
system: systemPrompt,
messages: [{ role: 'user', content: text }],
});
// 解析提取的景点列表
return content.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0 && line.length < 20);
}
关键点:
- 低温度(0.1)提高提取准确性
- 专用提示词优化提取效果
- 自动过滤无效结果
核心功能二:地图双向交互
功能描述
前端地图与后端 AI 通过 WebSocket 实现双向实时同步,AI 可以在地图上添加标记、绘制路线,用户也可以通过点击地图触发 AI 响应。
核心实现
1. 后端 → 前端:智能地图标记
文件:backend/src/agent/PlanningAgent.ts:246-431
// 流式响应中自动添加地图标记
async *generateStreamResponse(userInput: string, context: ExecutionContext) {
// 1. 生成 AI 回复(流式)
for await (const event of stream) {
const content = event.delta.text;
fullResponse += content;
yield content; // 流式返回给前端
}
// 2. 提取景点名称
const attractions = await this.extractAttractions(fullResponse);
// 3. 批量搜索 POI(地点信息)
const pois = await this.amapTool.batchSearch(attractions);
// 4. 发送地图标记事件到前端
context.websocket.emit('map:addMarkers', {
markers: pois.map(poi => ({
id: poi.id,
name: poi.name,
position: poi.location,
address: poi.address,
category: poi.category,
})),
});
// 5. 检测路线规划意图
const hasRouteIntent = this.detectRouteIntent(userInput, fullResponse);
if (hasRouteIntent && pois.length >= 2) {
// 规划多点路线
const routes = await this.routeTool.planMultiPointRoute(waypoints);
// 发送路线绘制事件
context.websocket.emit('map:drawRoute', {
path: fullPath,
color: '#10b981',
width: 6,
});
}
}
关键点:
- 流式响应完成后自动提取景点
- 调用高德地图 API 获取精确坐标
- 智能检测是否需要绘制路线
- 通过 WebSocket 事件驱动前端地图更新
2. 前端:WebSocket 事件监听
文件:frontend/hooks/useWebSocket.ts:92-149
// 批量添加地图标记
socketInstance.on('map:addMarkers', (data: any) => {
console.log('📍 Received markers from backend:', data.markers);
if (data.markers && Array.isArray(data.markers)) {
data.markers.forEach((marker: any) => {
addMarker({
id: marker.id,
position: marker.position,
label: marker.name,
data: {
name: marker.name,
address: marker.address,
category: marker.category,
},
});
});
}
});
// 绘制路线
socketInstance.on('map:drawRoute', (data: any) => {
addRoute({
id: `route-${Date.now()}`,
path: data.path,
color: data.color || '#4285F4',
width: data.width || 4,
});
});
// 地图飞行到指定位置
socketInstance.on('map:flyTo', (data: any) => {
if (data.center && data.zoom) {
setViewport({
center: data.center,
zoom: data.zoom,
});
}
});
关键点:
- 实时接收后端发送的地图操作指令
- 通过 Zustand store 更新状态触发 UI 重绘
- 支持标记、路线、视图控制等多种操作
3. 前端 → 后端:用户地图交互
文件:frontend/components/map/MapContainer.tsx:59-74
// 监听地图移动
map.on('moveend', () => {
const center = map.getCenter();
const zoom = map.getZoom();
setViewport({
center: [center.lng, center.lat],
zoom,
});
});
// 监听地图点击
map.on('click', (e: any) => {
emitMapEvent('map:clicked', {
location: [e.lnglat.lng, e.lnglat.lat],
});
});
// 监听标记点击
marker.on('click', () => {
emitMapEvent('map:markerClicked', {
markerId: markerData.id,
location: markerData.position,
name: markerData.label,
});
});
关键点:
- 捕获用户地图操作事件
- 通过 WebSocket 发送到后端
- 后端可基于用户操作生成智能响应
4. 高德地图 POI 搜索工具
文件:backend/src/tools/AmapSearchTool.ts:62-103
// 批量搜索景点并过滤川西范围
async batchSearch(names: string[]): Promise<POIResult[]> {
const chuanxiBounds = {
minLng: 99.0, maxLng: 104.0, // 经度范围
minLat: 27.0, maxLat: 33.0 // 纬度范围
};
for (const name of names) {
const pois = await this.searchPOI(name);
// 只保留川西范围内的 POI
const validPoi = pois.find(poi => {
const [lng, lat] = poi.location;
return lng >= chuanxiBounds.minLng && lng <= chuanxiBounds.maxLng &&
lat >= chuanxiBounds.minLat && lat <= chuanxiBounds.maxLat;
});
if (validPoi) {
results.push(validPoi);
}
}
return results;
}
关键点:
- 调用高德地图 Web API 搜索 POI
- 地理围栏过滤:只返回川西地区的景点
- 支持批量搜索提高效率