@Tool写对了,AI为什么还是犯傻?Spring AI Tool Calling实战复盘
这是「普通Java开发者的AI提效笔记」第3篇。第1篇聊了为什么学AI Agent,第2篇讨论了CRUD程序员会不会失业,这篇开始讲怎么做。
demo跑通了,但AI好像不太聪明
上周我用Spring AI Alibaba搭了个文创推荐助手——输入城市+送礼对象+预算,AI推荐伴手礼。
ChatClient + Structured Output跑通了,效果看着还行:
用户:我想给青岛的朋友带点文创伴手礼,预算200左右
AI:推荐以下产品:
1. 青岛贝壳风铃 - 约80元
2. 八大关风景明信片 - 约30元
3. 海边手工皂 - 约50元
看着挺智能对吧?但这是纯靠LLM自己的知识在编——它不知道今天故宫有什么新品、张一元茉莉花茶多少钱、大栅栏那家兔爷店还开门吗?价格也是AI编的,你去淘宝搜根本对不上。
所以我加了@Tool,让AI能"搜索"真实数据。
结果?AI反而变蠢了。
用户说"预算50",AI传maxPrice=100;category参数传了"特色"而不是"文创"……
跑通demo和让AI真的好用之间,差的不是代码,是 @Tool的description怎么写。
先说项目:我为什么做这个
帮同学挑北京文创伴手礼,5个朋友+2个老师+1个小宝宝,不同对象不同风格,逛了大栅栏走断腿,最后还是没买齐。
所以我想做:告诉它"我去哪、送谁、预算多少",它给我一份带购买指引的推荐清单。
技术选型:Spring Boot 3.5 + Spring AI Alibaba + @Tool注解。项目名叫「文搭」(文创搭子)。
核心坑:@Tool的description写不好,AI就是个智障
这是我在加@Tool后遇到的第一个、也是最大的坑。
AI开始犯傻了
我注册了三个工具:searchProducts(搜索产品)、searchShops(搜索店铺)、allocateBudget(预算分配)。然后输入"我想给朋友买个北京特色文创,预算50左右"。
AI的行为:
- searchProducts传了
category="特色",而不是"文创"——匹配不到数据 - searchProducts传了
maxPrice=100,用户明明说50 - 用户没问店铺,AI把searchShops也调了
- searchShops的district传了null——AI不知道该填什么
一句话总结:AI把所有工具都调了,参数还全传错了。
为什么?因为description写得太模糊
我的第一版@Tool是这样写的:
@Tool(description = "搜索文创产品")
public String searchProducts(
@ToolParam(description = "城市") String city,
@ToolParam(description = "品类") String category,
@ToolParam(description = "最低价") double minPrice,
@ToolParam(description = "最高价") double maxPrice)
这和写注释有什么区别? 你自己看一眼"品类"两个字,你知道该填"文创"还是"特色"还是"冰箱贴"吗?你不知道,AI也不知道。
改成详细的description,AI立刻变聪明
@Tool(description = "根据用户提供的城市和品类关键词搜索文创产品,返回产品名称、价格区间和简要描述。当用户需要为特定人群查找具体文创产品时使用此工具,会根据城市筛选本地特色产品并按预算范围过滤。")
public String searchProducts(
@ToolParam(description = "要搜索的城市名称,如'北京'、'西安'、'成都'") String city,
@ToolParam(description = "文创品类关键词,如'冰箱贴'、'茶叶'、'书签'、'布艺'") String category,
@ToolParam(description = "预算下限,单位元") double minPrice,
@ToolParam(description = "预算上限,单位元") double maxPrice)
同样的用户输入,AI的行为:
- searchProducts传了
category="文创"✓ - searchProducts传了
maxPrice=50.0✓ - 只调了searchProducts,没调searchShops ✓
- 推荐结果精确匹配用户需求 ✓
同样的用户输入:"我想给朋友带点青岛特色文创伴手礼,预算200左右",看AI传给Tool的参数和返回结果的差异:
❌ 模糊description → AI传参:
{
"city": "青岛",
"recipient": null, // AI不知道该填什么
"budget": null // 预算信息丢失
}
❌ 模糊description → Tool返回:
{
"recipientRecommendations": [{
"recipient": {
"role": "通用", // 没区分对象
"budgetPer": 0 // 没预算限制
},
"recommendations": [{
"productName": "青岛风景明信片套装",
"referencePrice": "35元",
"purchaseChannel": "景区门店"
}, {
"productName": "贝壳手链",
"referencePrice": "20元",
"purchaseChannel": "栈桥摊位"
}]
}]
}
35块钱的明信片和20块钱的贝壳手链,送朋友拿得出手吗?预算200完全不沾边。
✅ 详细description → AI传参:
{
"city": "青岛",
"recipient": "朋友", // 从上下文推断
"budget": 200 // 预算精准提取
}
✅ 详细description → Tool返回:
{
"recipientRecommendations": [{
"recipient": {
"role": "朋友",
"count": 1,
"budgetPer": 200,
"tags": ["文创", "地域特色"]
},
"recommendations": [{
"productName": "青岛啤酒主题文创礼盒(含迷你啤酒杯+定制瓶起子+啤酒味软糖)",
"reason": "融合青岛最具代表性的啤酒文化,兼具趣味性与纪念价值",
"referencePrice": "158元",
"purchaseAdvice": "青岛啤酒博物馆官方商店或栈桥附近文创店",
"purchaseChannel": "线上线下均可"
}, {
"productName": "崂山茶+海藻纤维手作书签礼盒",
"reason": "崂山茶为地理标志产品,搭配海藻纤维书签,体现山海共生理念",
"referencePrice": "179元",
"purchaseAdvice": "崂山区北宅街道茶文化体验馆,或'青岛文旅旗舰店'天猫店",
"purchaseChannel": "线上线下均可"
}]
}]
}
158元的啤酒文创礼盒和179元的崂山茶礼盒,预算匹配、对象匹配、还有购买指引。同一个接口,同一个AI,description写法不同,结果天差地别。
核心观点:description不是写给人看的注释,是写给AI看的"使用说明书"。 你给实习生写需求,写"帮我查一下"和写"帮我查一下最近3天A股AI板块涨幅前5的股票,要收盘价和涨跌幅",结果能一样吗?AI也一样。
@ToolParam也一样,给AI举例比解释更有效。 写"品类"不如写"文创品类关键词,如'冰箱贴'、'茶叶'",AI看到例子就知道该填什么了。
坑2:AI调工具报错了,然后呢?
搜索API偶尔会超时或返回空结果,这时候AI有两种反应:要么把错误信息原样甩给用户(用户看到一堆Java异常),要么直接卡住不知道该怎么办。
解法:工具方法的返回值不只是给用户看的,更是给AI看的。
@Tool(description = "根据城市和品类关键词搜索文创产品...")
public String searchProducts(String city, String category, double minPrice, double maxPrice) {
try {
String result = productService.search(city, category, minPrice, maxPrice);
if (result == null || result.isEmpty()) {
return "未找到符合条件的文创产品,建议扩大预算范围或更换品类关键词重试";
}
return result;
} catch (Exception e) {
return "搜索服务暂时不可用:" + e.getMessage() + "。请尝试换个关键词重试,或稍后再试";
}
}
关键在于:返回的错误信息要让AI理解并决定下一步。 "搜索无结果,建议换个关键词"——AI看到这句话,就知道该帮用户换个关键词重试,而不是原地报错。
再配合System Prompt里加一句:"如果工具返回错误信息,请根据提示调整参数重试,或向用户说明情况。" AI遇到报错就能自己处理了。
坑3:System Prompt才是灵魂,工具只是手
我注册了3个工具,结果AI每次都3个全调。用户明明只问了"有什么文创推荐",AI把searchShops也调了,还调了allocateBudget。
为什么?因为System Prompt里没告诉AI"什么时候用什么工具"。
改之前我的System Prompt就一句话:
你是文搭,一个文创推荐助手。
改之后:
你是「文搭」,一个专业的文创伴手礼推荐助手。
用户会告诉你去哪个城市、要送给谁、预算多少。
你可以使用以下工具:
1. searchProducts - 当用户需要查找具体文创产品时使用
2. searchShops - 当用户需要线下购买地址或店铺信息时使用
3. allocateBudget - 当用户给出总预算需要为多个对象分配时使用
规则:
- 不要同时调用所有工具,根据用户需求选择合适的工具
- 如果用户只问推荐什么,只调用searchProducts
- 如果用户问去哪买,再调用searchShops
- 如果用户有多个送礼对象且需要分配总预算,才调用allocateBudget
改完之后,AI终于知道什么时候该用什么工具了。
很多人以为Tool Calling的核心是代码,其实是提示词——工具是AI的手,System Prompt是AI的脑子。两手都要硬,AI才不犯傻。
最终效果
加上@Tool + 详细description + 友好错误处理 + System Prompt之后,效果是这样的:
AI精准地只调用了searchProducts,参数准确,推荐结果包含真实的产品信息和购买建议。
从demo跑通到AI真的好用,差的就是description这几个字。
给后来者的建议
- @Tool的description比代码逻辑更重要——这是AI理解工具的唯一依据,写详细不丢人
- @ToolParam也要写详细,举例比解释更有效——"如'冰箱贴'、'茶叶'"比"品类"好100倍
- System Prompt + 工具description = AI的"脑子 + 手" ——两手都要硬
- 工具方法的错误处理不是防御性编程,是给AI的决策依据——让AI知道失败了该怎么办
- AI输出不可控是常态,Structured Output是刚需——别让AI自由发挥
还记得开头那个预算50 AI传100的蠢操作吗?改完description后,同样的接口、同样的AI模型,参数精准、结果靠谱。不是AI蠢,是我们没说清楚。