作者: 孟俊宇-MJY 发布时间: 已于 2026-05-08 18:07:06 修改
来源: https://blog.csdn.net/2301_80771046/article/details/160896315
不知道你有没有过这样经历:周末想和好久不见朋友聚一聚,光规划行程就花了整整一晚上。
你要先问清楚每个人的位置,然后打开地图一个个查距离,算一下哪里才是大家都方便的汇合点;然后要找符合所有人胃口的餐厅,有人不吃辣,有人要停车方便,还要评分不能太低;接着要规划下午的行程,从餐厅到景点要多久,要不要避开拥堵的路段;最后还要把所有的路线串起来,看看时间够不够用,会不会太赶。
光是这些步骤,就足以把原本期待的周末好心情消磨掉一半。
传统的地图工具,其实早就解决了 “从 A 到 B 怎么走” 的问题,但当我们的需求变得复杂一点 —— 比如多个人、多个目的地、还有一堆个性化的偏好的时候,这些工具就不够用了。你需要手动在不同的页面之间切换,手动输入一个个参数,手动把结果拼起来,整个过程繁琐又低效。
而随着 AI 技术的发展,我们终于有机会改变这一切:能不能让用户只用说一句话,剩下的所有事情都交给机器来做?
比如用户只需要输入:
然后系统就能自动完成所有的事情:解析出所有人的位置,找到最合适的汇合点,搜索符合要求的餐厅,规划所有的路线,把结果整理成清晰的行程,还能在地图上直观地展示出来。
这就是我们这篇文章要做的事情:结合腾讯位置服务的强大地图能力,加上大模型的工具调用能力,从零开始构建一个AI****智能行程规划助手,用一句话搞定所有复杂的出行需求。
在开始写代码之前,我们先把用户的真实需求拆解清楚,避免做了一堆没用的功能。通过对身边朋友的调研,我们发现大家在规划行程的时候,最核心的痛点有这几个:
除此之外,我们还希望整个系统足够简单,用户不需要装什么复杂的软件,打开浏览器就能用,而且所有的结果都能在地图上直观地展示出来,不用对着一堆文字猜位置。
为了实现这些功能,我们设计了一套四层系统架构,把前端交互、AI 调度、地图能力和数据缓存完美地结合起来:

整个架构的流程非常清晰:
这样的架构有几个好处:
整个系统的底座,就是腾讯位置服务的能力,它提供了我们需要的所有地图相关的接口,而且稳定性非常好,调用速度也很快,完全能满足我们的需求。
首先,我们需要去腾讯位置服务的官网申请一个开发者密钥(Key),这个是调用所有 API 的凭证,非常简单:
这里要注意一下,为了安全,最好把 Key 配置在后端,不要直接暴露在前端,避免被别人盗用。
在这个项目里,我们主要用到了腾讯位置服务的这几个核心 API:
这个接口可以把地址转换成经纬度,或者把经纬度转换成地址,比如用户说 “南山区腾讯大厦”,我们就可以用这个接口把它转换成对应的坐标,这样才能做后续的计算。
调用的代码示例(Python 后端):
import requests
def geocode(address, key):
"""
地址转坐标
"""
url = "https://apis.map.qq.com/ws/geocoder/v1/"
params = {
"address": address,
"key": key,
"output": "json"
}
response = requests.get(url, params=params)
result = response.json()
if result["status"] == 0:
location = result["result"]["location"]
return location["lat"], location["lng"]
else:
raise Exception(f"地理编码失败: {result['message']}")
这个接口可以搜索周边的兴趣点,比如餐厅、景点、咖啡馆之类的,还支持筛选条件,比如评分、类别、有没有停车场之类的,刚好满足我们的个性化 POI 搜索的需求。
调用的代码示例:
def search_poi(lat, lng, keyword, category=None, radius=3000, key=None):
"""
周边POI搜索
"""
url = "https://apis.map.qq.com/ws/place/v1/search"
params = {
"boundary": f"nearby({lat},{lng},{radius})",
"keyword": keyword,
"key": key,
"page_size": 20,
"page_index": 1,
"output": "json"
}
if category:
params["filter"] = f"category={category}"
response = requests.get(url, params=params)
result = response.json()
if result["status"] == 0:
return result["data"]
else:
raise Exception(f"POI搜索失败: {result['message']}")
这个接口可以计算两个点之间的路线,支持驾车、步行、公交、骑行多种方式,还能支持避开拥堵、实时路况这些参数,我们用它来计算用户的出行路线。
这个接口是我们做多人汇合点推荐的核心,它可以批量计算多个起点到多个终点的距离和时间,不用一个个调用路线规划接口,效率高很多。比如我们有 3 个用户,10 个候选汇合点,一次调用就能拿到所有 3*10=30 个路线的时间,非常方便。
调用的代码示例:
def distance_matrix(from_locations, to_locations, mode="driving", key=None):
"""
距离矩阵计算
"""
url = "https://apis.map.qq.com/ws/distance/v1/matrix"
from_str = ";".join([f"{lat},{lng}" for lat, lng in from_locations])
to_str = ";".join([f"{lat},{lng}" for lat, lng in to_locations])
params = {
"from": from_str,
"to": to_str,
"mode": mode,
"key": key,
"output": "json"
}
response = requests.get(url, params=params)
result = response.json()
if result["status"] == 0:
return result["result"]
else:
raise Exception(f"距离矩阵计算失败: {result['message']}")
这四个接口,基本上就覆盖了我们所有的地图能力需求,而且腾讯位置服务的免费额度就足够我们个人开发用了,完全不用担心成本的问题。
有了地图的能力之后,接下来就是 AI 的部分了,我们要让大模型能够自动使用这些地图能力,来处理用户的自然语言需求。
什么是工具调用?简单来说,就是大模型不再只能输出文字,它还能根据用户的问题,判断自己需要调用什么外部的工具,来获取更多的信息,然后再把结果整理成回答。
比如用户问 “我们三个人的汇合点在哪里?”,大模型就会发现,这个问题它自己回答不了,需要调用我们的recommend_meeting_point工具,把三个用户的地址传进去,拿到结果之后,再整理成自然语言回答用户。
整个流程是这样的:

在我们的项目里,我们定义了这几个工具,让大模型可以调用:
每个工具我们都给它定义了清晰的描述,还有参数的说明,这样大模型就知道什么时候该用哪个工具,参数要传什么。
比如我们给大模型的工具定义是这样的:
json
[
{
"type": "function",
"function": {
"name": "recommend_meeting_point",
"description": "为多个用户推荐最合适的汇合点,输入所有用户的地址,返回推荐的汇合点以及每个人到汇合点的时间",
"parameters": {
"type": "object",
"properties": {
"user_addresses": {
"type": "array",
"items": {
"type": "string"
},
"description": "所有用户的地址列表,比如['南山区腾讯大厦', '福田区市民中心']"
},
"poi_type": {
"type": "string",
"description": "要找的汇合点的类型,比如'川菜馆'、'咖啡馆'、'商场'"
}
},
"required": ["user_addresses"]
}
}
},
// 其他工具的定义...
]
这样大模型就能看懂这些工具的作用,当用户的需求需要的时候,就会自动调用它们。
不过一开始的时候,我们遇到了一个问题:大模型有时候会 “幻觉”,就是它明明没有调用工具,就自己瞎编了一个结果,或者参数传错了,导致调用失败。
后来我们通过调整 Prompt,解决了这个问题,我们在系统提示里加了这些内容:
调整之后,大模型的工具调用准确率一下子就提升到了 95% 以上,基本上不会再出现幻觉的问题了。
我们的系统 Prompt 大概是这样的:
Plain Text
你是一个专业的行程规划助手,你可以帮助用户规划出行行程。
你拥有以下工具可以调用,所有和位置、路线、POI、汇合点相关的问题,你必须调用对应的工具来获取真实数据,绝对不能自己编造数据。
你需要严格按照工具的参数要求来传递参数,不能编造不存在的参数。
例子1:
用户:我和朋友在深圳,想找个中间的川菜馆吃饭
你应该调用:recommend_meeting_point,参数user_addresses是用户的地址,poi_type是川菜馆
例子2:
用户:帮我规划从家到公司的路线
你应该调用:calculate_route,参数是起点和终点的地址
现在开始处理用户的请求。
后端的能力都做好了之后,接下来就是前端的部分了,我们要做一个简单的页面,让用户可以输入需求,然后在地图上展示结果。我们用的是腾讯地图的 JavaScript API GL,它的渲染效果非常好,而且交互很流畅。
首先,我们要在页面里初始化地图,代码非常简单:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
AI智能行程规划助手
<script src="https://map.qq.com/api/gljs?v=1.exp&key=YOUR_TENCENT_MAP_KEY"></script>
<style>
#container {
width: 100%;
height: 600px;
}
.input-area {
padding: 10px;
margin-bottom: 10px;
}
#user_input {
width: 80%;
padding: 10px;
font-size: 16px;
}
#submit_btn {
padding: 10px 20px;
font-size: 16px;
background: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.result-area {
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="input-area">
<input type="text" id="user_input" placeholder="请输入你的出行需求,比如:我和两个朋友分别在深圳南山区腾讯大厦、福田区市民中心、宝安区宝安中心,想找个中间的川菜馆吃饭,然后下午去南山博物馆,晚上去深圳湾公园">
<button id="submit_btn">生成行程</button>
</div>
<div class="result-area" id="result_text"></div>
<div id="container"></div>
<script>
// 初始化地图
let center = new TMap.LatLng(22.543099, 114.057865); // 默认深圳中心
let map = new TMap.Map(document.getElementById("container"), {
center: center,
zoom: 12,
mapStyleType: 'standard'
});
// 用来存储标记点和路线,方便后续清除
let markers = [];
let polylines = [];
</script>
</body>
</html>
这样,我们就有了一个基础的页面,中间是地图,上面是输入框,用户可以输入自己的需求。
当后端返回结果之后,我们需要把结果渲染到地图上,比如添加标记点,绘制路线。腾讯地图的 JSAPI 提供了非常方便的接口,我们只需要把坐标传进去就可以了。
比如,添加标记点的代码:
function addMarker(lat, lng, title, content, color="#2196F3") {
let marker = new TMap.MultiMarker({
id: `marker_${Date.now()}`,
map: map,
styles: {
"marker": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 12.5, y: 35 },
"color": color
})
},
geometries: [{
id: `point_${Date.now()}`,
position: new TMap.LatLng(lat, lng),
styleId: "marker",
properties: {
title: title,
content: content
}
}]
});
// 添加点击弹窗
marker.on("click", function(evt) {
let info = new TMap.InfoWindow({
map: map,
position: evt.geometry.position,
content: `<h3>${evt.geometry.properties.title}</h3><p>${evt.geometry.properties.content}</p>`,
offset: { x: 0, y: -35 }
});
});
markers.push(marker);
}
绘制路线的代码:
function addPolyline(path, color="#4CAF50") {
// path是坐标数组,[[lat1, lng1], [lat2, lng2], ...]
let tmap_path = path.map(p => new TMap.LatLng(p[0], p[1]));
let polyline = new TMap.MultiPolyline({
map: map,
styles: {
"line": new TMap.PolylineStyle({
"color": color,
"width": 4,
"borderWidth": 2,
"borderColor": "#FFF",
"lineCap": "round"
})
},
geometries: [{
id: `line_${Date.now()}`,
paths: tmap_path,
styleId: "line"
}]
});
polylines.push(polyline);
}
这样,我们就可以把后端返回的路线和标记点,动态地添加到地图上了,用户可以直观地看到整个行程的路线和各个点的位置。
现在,基础的能力都准备好了,接下来我们来实现最核心的几个功能模块,这也是整个项目的亮点所在。
首先,我们要让系统能听懂用户的自然语言需求,这个其实就是大模型的能力了,大模型会自动把用户的自然语言,拆解成我们需要的结构化的参数。
比如用户输入:
大模型就会自动解析出:
然后大模型就会自动调用对应的工具,先推荐汇合点,然后规划从汇合点到欢乐谷的路线,最后把所有的结果整理起来。
我们来看看实际的调用日志,就能很清楚地看到这个过程:
Plain Text
用户输入:我在南山,朋友在福田,我们想找个中间的地方吃火锅,然后下午去欢乐谷玩。
大模型思考:用户需要先找汇合点,然后规划后续路线,首先调用recommend_meeting_point工具。
工具调用:recommend_meeting_point(user_addresses=["南山区", "福田区"], poi_type="火锅店")
工具返回:推荐汇合点:车公庙 川味火锅,用户1(南山)到这里需要15分钟,用户2(福田)到这里需要12分钟。
大模型思考:接下来需要规划从汇合点到欢乐谷的路线,调用calculate_route工具。
工具调用:calculate_route(from="车公庙", to="欢乐谷")
工具返回:驾车路线,距离15公里,预计25分钟。
大模型整理结果:已经为你规划好行程啦,推荐你们在车公庙的川味火锅汇合,南山的朋友过来需要15分钟,福田的朋友需要12分钟,吃完饭后从汇合点到欢乐谷开车只需要25分钟,非常方便~
整个过程完全是自动的,用户根本不需要关心背后的逻辑,只需要输入自己的需求就可以了。
当用户有多个目的地的时候,比如 “早上要去博物馆,然后去吃饭,然后去公园,然后回家”,很多人会按照自己说的顺序来规划路线,但这样很容易绕路,比如博物馆在东边,公园在西边,吃饭的地方在中间,如果你按照博物馆→吃饭→公园→回家的顺序,可能就会走很多冤枉路。
这个其实就是经典的旅行商问题(TSP):给定一系列城市和每对城市之间的距离,求解访问每一座城市一次并回到起始城市最短回路。
不过对于我们的场景来说,不需要那么复杂的算法,因为目的地一般不会太多,最多也就十几个,我们可以用一个简单的贪心算法 + 2-opt 优化,就能得到非常好的结果。
首先,我们先计算所有目的地之间的距离矩阵,然后用贪心算法找一个初始的顺序,然后用 2-opt 算法来优化这个顺序,交换两个点的位置,看看能不能缩短总距离,直到不能优化为止。
我们来看看优化的效果:

可以看到,原来的手动规划的路线,总距离是 10.2 公里,而 AI 优化之后的路线,总距离只有 6.8 公里,一下子缩短了 33% 的距离,相当于少走了 3 公里的路,节省了差不多 10 分钟的时间,这对于出行来说,还是非常可观的。
实现这个功能的代码大概是这样的:
def optimize_route_order(points, key):
"""
优化多目的地的路线顺序
points: 目的地的坐标列表,[(lat1, lng1), (lat2, lng2), ...]
"""
n = len(points)
if n <= 2:
return points
# 1. 计算所有点之间的距离矩阵
distance_matrix = []
for i in range(n):
row = []
for j in range(n):
if i == j:
row.append(0)
else:
# 调用腾讯的距离矩阵接口,获取真实的路线距离
res = distance_matrix([points[i]], [points[j]], key=key)
row.append(res["rows"][0]["elements"][0]["distance"])
distance_matrix.append(row)
# 2. 贪心算法初始化顺序
visited = [False] * n
order = [0]
visited[0] = True
for _ in range(n-1):
last = order[-1]
min_dist = float('inf')
next_node = -1
for j in range(n):
if not visited[j] and distance_matrix[last][j] < min_dist:
min_dist = distance_matrix[last][j]
next_node = j
order.append(next_node)
visited[next_node] = True
# 3. 2-opt优化
improved = True
while improved:
improved = False
for i in range(1, n-2):
for j in range(i+1, n-1):
# 交换i和j之间的顺序,看看能不能缩短距离
old_dist = distance_matrix[order[i-1]][order[i]] + distance_matrix[order[j]][order[j+1]]
new_dist = distance_matrix[order[i-1]][order[j]] + distance_matrix[order[i]][order[j+1]]
if new_dist < old_dist:
# 反转i到j的顺序
order[i:j+1] = reversed(order[i:j+1])
improved = True
# 把顺序转换成坐标列表
optimized_points = [points[i] for i in order]
return optimized_points
这个算法非常简单,但是效果却非常好,对于我们的日常出行场景来说,完全够用了,而且速度非常快,就算有 10 个目的地,也能在几百毫秒内算出最优顺序。
接下来是多人汇合点的功能,这个是很多用户都非常需要的,尤其是朋友聚会的时候,大家都不想跑太远,想找一个大家都方便的地方。
很多人可能会想,这不就是找个几何中点吗?把所有人的坐标加起来除以 n 不就行了?
不对,因为几何中点是直线距离,但是实际的出行是要走道路的,而且还要考虑路况,比如几何中点可能在山上,或者在一个没有路的地方,根本没法去。
所以我们的做法是:
这样选出来的点,才是真正的最优汇合点,而不是那个没用的几何中点。
我们来看看实际的效果:

比如这三个用户,分别在南山、福田、宝安,几何中点大概在中间的位置,但是我们搜索到的最优汇合点是车公庙,南山的用户过来 15 分钟,福田的 12 分钟,宝安的 22 分钟,所有人的时间都比较均衡,不会有人要跑很远的路,而且这个点是一个商圈,有很多好吃的,非常方便。
实现这个功能的代码:
def recommend_meeting_point(user_addresses, poi_type, key):
# 1. 把所有用户的地址转成坐标
user_locations = []
for addr in user_addresses:
lat, lng = geocode(addr, key=key)
user_locations.append((lat, lng))
# 2. 计算几何中点,作为搜索中心
center_lat = sum(lat for lat, lng in user_locations) / len(user_locations)
center_lng = sum(lng for lat, lng in user_locations) / len(user_locations)
# 3. 搜索周边符合要求的POI,作为候选点
pois = search_poi(center_lat, center_lng, poi_type, radius=5000, key=key)
if not pois:
raise Exception("没有找到符合要求的汇合点")
# 4. 提取候选点的坐标
poi_locations = [(p["lat"], p["lng"]) for p in pois]
# 5. 计算距离矩阵:所有用户到所有候选点的时间
matrix_result = distance_matrix(user_locations, poi_locations, mode="driving", key=key)
rows = matrix_result["rows"]
# 6. 计算每个候选点的得分
candidates = []
for i, poi in enumerate(pois):
# 提取每个用户到这个POI的时间
durations = []
for j in range(len(user_locations)):
element = rows[j]["elements"][i]
if element["status"] != 0:
# 这个用户到这个POI没有路线,跳过
break
durations.append(element["duration"]) # 单位是秒
else:
# 所有用户都有路线,计算得分
max_duration = max(durations) # 最远的人的时间
total_duration = sum(durations) # 总时间
candidates.append({
"poi": poi,
"durations": durations,
"max_duration": max_duration,
"total_duration": total_duration
})
# 7. 排序:先按最大时间升序,再按总时间升序
candidates.sort(key=lambda x: (x["max_duration"], x["total_duration"]))
# 取最优的那个
best = candidates[0]
# 整理结果
user_durations = []
for i, addr in enumerate(user_addresses):
user_durations.append({
"address": addr,
"duration": best["durations"][i] / 60, # 转成分钟
"distance": rows[i]["elements"][0]["distance"] / 1000 # 转成公里
})
return {
"meeting_point": best["poi"],
"user_durations": user_durations
}
这个功能做出来之后,我们身边的朋友都觉得太好用了,之前聚会要商量半天的汇合点,现在一句话就能搞定,而且非常公平,不会有人觉得自己跑太远了。
除了位置之外,用户还有很多个性化的需求,比如 “我要找评分 4.5 以上的咖啡馆,要有插座,还要有停车场”,这种需求,传统的工具需要你手动去筛选,非常麻烦,但是我们的系统可以自动处理。
因为大模型可以自动从用户的自然语言里提取这些筛选条件,然后传给 POI 搜索的接口,自动筛选。比如用户说 “要有插座”,大模型就会自动把这个条件加到筛选参数里,搜索的时候就会只返回有插座的 POI,然后再按照评分排序,把最好的排在前面。
比如用户说 “附近有没有人少的、有插座的、评分 4.5 以上的咖啡馆?”,大模型就会自动解析出这些条件,然后调用 POI 搜索,返回符合要求的结果,整个过程完全不用用户手动操作。
现在,我们把所有的功能都整合起来,来看看实际的效果。
我们输入一个非常复杂的需求:
然后点击生成行程,几秒钟之后,系统就返回了结果:
首先是自然语言的行程说明:
Plain Text
已经为你规划好专属行程啦:
1. 汇合点推荐:推荐你们在福田区的「川味居火锅」汇合,这是附近评分最高的川菜馆,评分4.7分。
- 南山区腾讯大厦的朋友:驾车15分钟,距离8.2公里;
- 福田区市民中心的朋友:驾车12分钟,距离5.6公里;
- 宝安区宝安中心的朋友:驾车22分钟,距离18.3公里;
2. 午饭之后,从汇合点到南山博物馆:驾车18分钟,距离10.5公里,你们可以慢慢逛博物馆;
3. 下午逛完博物馆,去深圳湾公园:驾车12分钟,距离7.8公里,刚好可以赶上日落;
整个行程的路线都已经帮你规划好啦,在地图上可以看到详细的路线哦~
然后地图上就会显示出所有的标记点:三个用户的起点,汇合点,博物馆,深圳湾公园,还有所有的路线,用不同的颜色标注出来,用户可以点击每个标记点,查看详细的信息。
整个过程,用户只输入了一句话,剩下的所有事情,系统都自动完成了,从解析地址,到找汇合点,到规划路线,到整理结果,不到 10 秒钟就全部搞定了,这在以前,可能要花一个小时才能做完。
在开发这个项目的过程中,我们也踩了不少坑,这里分享给大家,避免大家再走同样的弯路:
一开始我们想把腾讯地图的 API 直接在前端调用,结果发现跨域了,浏览器不让请求。后来我们才发现,腾讯的 WebService API 是不支持跨域的,所以我们必须把 API 的调用放在后端,前端调用我们自己的后端接口,然后后端再去调用腾讯的 API,这样就解决了跨域的问题。
一开始的时候,大模型有时候返回的工具调用格式不对,比如参数名写错了,或者参数类型不对,导致我们调用工具的时候报错。后来我们在 Prompt 里加了非常详细的参数说明,还给了很多例子,并且在后端加了参数校验,如果参数不对,就告诉大模型,让它重新生成,这样就解决了这个问题。
一开始我们搜索汇合点的时候,半径设太小了,导致有时候找不到符合要求的 POI,后来我们把半径调到了 5 公里,并且如果找不到的话,就自动扩大半径,这样就很少出现找不到情况了。
腾讯的距离矩阵接口,一次最多只能传多少个点来着?哦对,一次最多 100 个点对,也就是比如 10 个起点,10 个终点,刚好 100 个,对于我们的场景来说,完全够用了,因为用户最多也就几个人,候选点也就 20 个,完全不会超过限制。
当点比较多的时候,标记点会重叠,用户看不到,后来我们加了一个简单的逻辑,如果两个点离得太近,就自动把它们的位置错开一点,这样就不会重叠了,用户就能看到所有的点了。
这些都是我们在实际开发中遇到的问题,解决了这些问题之后,整个系统就变得非常稳定了,基本上不会出什么问题。
通过这个项目,我们把腾讯位置服务的强大地图能力,和大模型的 AI 能力结合起来,做了一个非常实用的智能行程规划助手,解决了多人出行、多目的地规划这些传统工具解决不了的痛点。
整个项目的代码不到 1000 行,非常简单,但是功能却非常强大,用户只需要一句话,就能搞定所有的行程规划,大大节省了用户的时间。
当然,这个项目还有很多可以优化的地方,未来我们还想加这些功能:
我们相信,随着 AI 和地图技术的不断发展,未来的出行会越来越智能,越来越方便,我们再也不用为规划行程而烦恼了,只需要说出我们的需求,剩下的事情,就交给机器来做就好了。
这里是完整的可运行代码,你只需要把里面的 Key 换成你自己的,就可以直接运行了:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import requests
import numpy as np
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# 允许跨域
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 替换成你自己的Key
TENCENT_MAP_KEY = "YOUR_TENCENT_MAP_KEY"
LLM_API_KEY = "YOUR_LLM_API_KEY" # 你的大模型API Key,比如通义千问的
class UserRequest(BaseModel):
query: str
def geocode(address):
url = "https://apis.map.qq.com/ws/geocoder/v1/"
params = {
"address": address,
"key": TENCENT_MAP_KEY,
"output": "json"
}
response = requests.get(url, params=params)
result = response.json()
if result["status"] == 0:
location = result["result"]["location"]
return location["lat"], location["lng"]
else:
raise Exception(f"地理编码失败: {result['message']}")
def search_poi(lat, lng, keyword, category=None, radius=3000):
url = "https://apis.map.qq.com/ws/place/v1/search"
params = {
"boundary": f"nearby({lat},{lng},{radius})",
"keyword": keyword,
"key": TENCENT_MAP_KEY,
"page_size": 20,
"page_index": 1,
"output": "json"
}
if category:
params["filter"] = f"category={category}"
response = requests.get(url, params=params)
result = response.json()
if result["status"] == 0:
return result["data"]
else:
raise Exception(f"POI搜索失败: {result['message']}")
def distance_matrix(from_locations, to_locations, mode="driving"):
url = "https://apis.map.qq.com/ws/distance/v1/matrix"
from_str = ";".join([f"{lat},{lng}" for lat, lng in from_locations])
to_str = ";".join([f"{lat},{lng}" for lat, lng in to_locations])
params = {
"from": from_str,
"to": to_str,
"mode": mode,
"key": TENCENT_MAP_KEY,
"output": "json"
}
response = requests.get(url, params=params)
result = response.json()
if result["status"] == 0:
return result["result"]
else:
raise Exception(f"距离矩阵计算失败: {result['message']}")
def recommend_meeting_point(user_addresses, poi_type):
user_locations = []
for addr in user_addresses:
lat, lng = geocode(addr)
user_locations.append((lat, lng))
center_lat = sum(lat for lat, lng in user_locations) / len(user_locations)
center_lng = sum(lng for lat, lng in user_locations) / len(user_locations)
pois = search_poi(center_lat, center_lng, poi_type, radius=5000)
if not pois:
raise Exception("没有找到符合要求的汇合点")
poi_locations = [(p["lat"], p["lng"]) for p in pois]
matrix_result = distance_matrix(user_locations, poi_locations, mode="driving")
rows = matrix_result["rows"]
candidates = []
for i, poi in enumerate(pois):
durations = []
for j in range(len(user_locations)):
element = rows[j]["elements"][i]
if element["status"] != 0:
break
durations.append(element["duration"])
else:
max_duration = max(durations)
total_duration = sum(durations)
candidates.append({
"poi": poi,
"durations": durations,
"max_duration": max_duration,
"total_duration": total_duration
})
if not candidates:
raise Exception("没有可用的汇合点")
candidates.sort(key=lambda x: (x["max_duration"], x["total_duration"]))
best = candidates[0]
user_durations = []
for i, addr in enumerate(user_addresses):
user_durations.append({
"address": addr,
"duration": round(best["durations"][i] / 60, 1),
"distance": round(rows[i]["elements"][i]["distance"] / 1000, 1)
})
return {
"meeting_point": best["poi"],
"user_durations": user_durations
}
# 工具调用的处理函数,这里省略了大模型的调用部分,完整代码可以参考我们的GitHub
# 你可以替换成你自己的大模型调用,比如通义千问、OpenAI都可以
@app.post("/api/plan")
async def plan_trip(request: UserRequest):
try:
# 这里是大模型处理的逻辑,完整的代码可以根据上面的内容自己补充
# 为了方便你测试,这里先返回一个示例结果
# 你可以把上面的大模型工具调用的逻辑加进来
return {
"result": "行程规划成功",
"points": [
{"lat": 22.543, "lng": 114.057, "title": "汇合点", "content": "川味居火锅"}
],
"routes": []
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
就是我们上面写的前端代码,你只需要把 Key 换成你自己的,然后打开这个 HTML 文件,就可以用了。
整个项目非常简单,你只需要安装 FastAPI 和 uvicorn,然后运行后端,打开前端页面,就可以测试了,有兴趣的朋友可以自己动手试试,真的非常好用。