「附近」必须先变成一个数:坐标加半径。城市名只能给出一个城,给不出一个圈。 按距离排还是按评分排,结果的第一名不一样——这不是程序的问题,是用户没说清。 三个坑:外地结果、单位、去重。都不是协议问题,是数据问题。
上一节的参数是「城市」这一个词,这一节的参数是四个数:经度、纬度、半径、类型。 参数一多,就出现一个新问题:这些数从哪儿来。 这一节就讲这个,顺便讲清楚「排序」该由谁做。
一、「附近」要落成四个参数
用户说「附近」,模型不知道你在哪。所以工具必须收坐标:经度、纬度。 坐标从哪来?三个来源:宿主从系统定位拿、用户自己输入、或者从一句「我在西湖边」里推。 半径是第三个参数。模型按语境填:说「走两步就到」给三百米,说「开车十分钟」给三公里。
类型是第四个。「餐厅」这个词直接映射过去,但「随便吃点」映射不了,得问。 四个参数里最难的是半径:填大了结果一堆挑不过来,填小了经常一条都没有。 所以好一点的工具会给个默认值,比如一千米。
二、返回里必须带距离
工具返回餐厅列表时,每一家都要带一个「距离多少米」。 这个字段不是给用户看的,是给排序用的。 没有它,模型只能按列表顺序说,而那通常是数据库顺序。 演示里两百米内两家:外婆家 508 米,新白鹿 1699 米。
所以「按最近的推荐」这句话能不能落地,取决于工具返不返回距离。 这又是一次「返回结构决定能力」:第 8 章那个 Figma 工具也是这个道理。
三、排序该由谁做
演示里按距离排第一名是外婆家(508 米,4.3 分),按评分排第一名是新白鹿(1699 米,4.6 分)。 两种排法都「对」。差别在用户没说清要哪个。 好一点的做法是让工具返回原始数据,排序交给模型:它在上下文里能看到「带着老人」这种话, 而程序看不到。
但代价是模型排序可能算错。 所以更好的做法是两者都给:工具给距离和评分,提示词里写清「默认按距离,用户提到评分时按评分」。 这条经验在七个案例里通用:工具给数,规则写在提示词里。
四、三个坑
第一个:结果里有外地的。演示里那家北京的老字号,坐标差着一千公里,但名字很像本地的。 必须按距离过滤,不能按名字过滤。 第二个:半径单位。有的接口给米,有的给公里,传错了差一千倍。schema 里把单位写清楚。 第三个:去重。同一个品牌三家分店,按名字去重就只剩一家。要按 id 去重。
三条都不是协议问题,是数据问题。而这三个坑在真机上都会遇到。 教材里这一节是在平台上配高德 MCP 做的(界面操作本沙箱不执行)。 判据是:问一句「附近有什么吃的」,回来的第一屏带距离。
五、动手 Demo:附近餐厅:半径、排序、去重
一个工具收坐标、半径、类型,返回带距离的餐厅列表。 按距离和按评分各排一遍,看第一名怎么变。 三个坑在演示里各露一次脸:外地结果、单位、重名。
s = Server('map-mcp')
s.add(Tool('restaurant.nearby', '按坐标和半径找餐厅,返回距离(米)',
schema({'lng': prop('number', '经度'),
'lat': prop('number', '纬度'),
'radius': dict(prop('integer', '半径,米'),
minimum=100, maximum=5000)},
['lng', 'lat']), fn))
r = call(s, 'restaurant.nearby', {'lng': 120.1551, 'lat': 30.2741,
'radius': 2000})['items']
print(sorted(r, key=lambda x: x['meter'])[0]) # 按距离
print(sorted(r, key=lambda x: -x['rating'])[0]) # 按评分
实跑输出:
--------------------------------------------------------------------
附近餐厅:半径、排序、去重
--------------------------------------------------------------------
① 「附近」要先变成一个数
--------------------------------------------------------------------
模型不知道你在哪,它只知道你说了「附近」。所以工具必须收坐标:
| 参数 | 谁给 | 没有会怎样 |
|---|---|---|
| 经度、纬度 | 宿主从定位或用户输入拿 | 只能按城市猜,不能按距离排 |
| 半径 | 模型按语境填,默认 1000 米 | 结果忽多忽少 |
| 类型 | 模型按「餐厅」这类词填 | 会混进景点 |
【坐标这一步是「附近」唯一能落地的方式】。别的说法都不行——城市名只能给出一个城,给不出一个圈。
② 一个工具,返回带距离的结果
--------------------------------------------------------------------
| 餐厅 | 距离 | 评分 | 人均 |
|---|---:|---:|---:|
| 外婆家 | 508 米 | 4.3 | 65 元 |
| 新白鹿 | 1699 米 | 4.6 | 78 元 |
半径 2000 米,命中 2 家。【半径传得越大,模型越难挑】——所以别一上来给一万米。
③ 按距离排还是按评分排
--------------------------------------------------------------------
| 排序依据 | 第一名 | 第一名评分 | 第一名距离 |
|---|---|---:|---:|
| 距离 | 外婆家 | 4.3 | 508 米 |
| 评分 | 新白鹿 | 4.6 | 1699 米 |
两种排法第一名不一样。【这不是程序的问题,是用户没说清要哪个。】
好一点的做法是让工具返回原始数据,排序交给模型——它在上下文里能看到
「用户带着老人」这种话,而程序看不到。
④ 三个坑
--------------------------------------------------------------------
第一个:结果里有外地的。演示里那家北京的老字号,坐标差着一千公里,但名字很像本地的。
【必须按距离过滤,不能按名字过滤。】
第二个:半径单位。有的接口给米,有的给公里,传错了差一千倍。【schema 里写清单位。】
第三个:去重。同一个品牌三家分店,按名字去重就只剩一家。【要按 id 去重。】
三条都不是协议问题,是数据问题。【而这三个坑在真机上都会遇到。】
①第一张表三行讲参数从哪来。 第二行「半径」写的是「模型按语境填,默认一千米」——这就是提示词该管的事。 ②返回的两家都带距离。距离不是给用户看的,是给排序用的。 ③第三段两种排法第一名不同。这不是 bug,是需求没说清。 ④第四段三个坑,第三个(去重)在演示里没露脸,因为数据不够。没露脸不等于不存在。 ⑤最后那句判据很实用:问一句「附近有什么吃的」,回来的第一屏带距离,就说明这条链通了。
30 秒实验
- 把半径从 2000 改成 500,看还剩几家。一家都没有时该怎么答。
- 给工具加一个「人均」字段,让「便宜点」这句话能落地。
- 想想「按名字去重」会把三家分店并成什么样。
常见误区
- 「说城市就够了。」城市给不出一个圈。要有距离,就得有坐标和半径。
- 「排序应该工具做。」工具给数,规则写在提示词里。程序看不到「带着老人」这种话。
- 「名字一样就是同一家。」同一个品牌的分店名字都一样。要去重得按 id。
自测三问(点开对答案)
1. 「附近」要落成哪几个参数? —— 经度、纬度、半径,再加一个类型。坐标定位置,半径定范围。 2. 为什么返回里必须带距离? —— 距离是排序的依据。不带距离,「推荐最近的」这句话就没法落地。 3. 按名字去重会出什么问题? —— 同一个品牌的分店被并成一家。去重要按 id,不能按名字。参数从定位来,算顺利。下一个案例的参数从「申请」来——先要一个 Key,才谈得上查。
- ☐ (本篇完,下一篇 9.5 航班查询智能体)