作为一名长期在AI应用开发一线的工程师,我发现很多开发者对工具调用(Tool Calling)这个关键技术存在理解偏差。今天我就通过一个实战案例——用LangChain4j+Jsoup实现博客园文章搜索工具,带大家彻底掌握工具调用的核心原理和实现细节。
工具调用本质上是一种"AI决策+本地执行"的协作模式。当用户提出"帮我查下博客园用户BNTang的最新文章"这类需求时,AI并不直接执行操作,而是分析意图后生成工具调用请求,由我们的程序实际执行网页抓取、数据处理等操作,最后将结构化结果返回给AI生成自然语言回复。这种模式完美结合了AI的理解能力和本地程序的执行能力。
完整的工具调用流程可以分为六个关键阶段:
关键点:工具执行完全发生在应用侧,AI服务器只负责决策和结果处理。这种架构既保障了数据隐私,又能利用本地计算资源。
在本案例中,我们选择以下技术组合:
选择LangChain4j而非Python生态的LangChain,主要基于以下考虑:
首先创建Maven项目,在pom.xml中添加必要依赖:
xml复制<dependencies>
<!-- LangChain4j 核心库 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>0.25.0</version>
</dependency>
<!-- Jsoup 网页抓取 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
<!-- 根据实际使用的AI模型添加 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-qianwen</artifactId>
<version>0.25.0</version>
</dependency>
</dependencies>
在tools包下创建CnblogsArticleTool类,这是整个功能的核心。我们使用@Tool注解声明这是一个可被AI调用的工具:
java复制@Slf4j
public class CnblogsArticleTool {
@Tool(name = "cnblogsSearch", value = """
从博客园获取最新文章。输入可以是:
- 博客园用户名(例如:'someUser')
- 完整的个人主页URL(例如:'https://www.cnblogs.com/someUser/')
可选择性地附加'|N'来限制结果数量,例如:'someUser|5'。
返回包含标题、链接、日期、摘要、阅读数、评论数、推荐数的JSON数组。
""")
public String searchCnblogsArticles(
@P("用户名或URL(可选地附加|限制数量)") String input) {
// 参数校验与解析
if (input == null || input.trim().isEmpty()) {
return errorResponse("Empty input");
}
// 解析输入参数
ParamInfo params = parseInput(input);
// 构建目标URL
String targetUrl = buildTargetUrl(params.username);
// 获取并解析HTML文档
Document doc = fetchDocumentWithRetries(targetUrl, 3, 8000);
if (doc == null) {
return errorResponse("Failed to fetch page");
}
// 提取文章信息
List<ArticleInfo> articles = extractArticles(doc, params.limit);
// 生成JSON响应
return toJsonResponse(articles);
}
// 其他辅助方法...
}
输入参数处理:
user|5表示最多返回5条)网页抓取优化:
HTML解析技巧:
.day)[置顶])数据去重处理:
创建AI服务配置类,将工具绑定到AI模型:
java复制@Configuration
public class AiServiceConfig {
@Bean
public AiCodeHelperService aiCodeHelperService(
ChatModel chatModel,
CnblogsArticleTool articleTool) {
return AiServices.builder(AiCodeHelperService.class)
.chatModel(chatModel)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.tools(articleTool)
.build();
}
}
关键配置项说明:
ChatModel:实际使用的AI模型实例ChatMemory:保留最近10条对话记录tools():注册我们的博客园文章搜索工具编写JUnit测试验证工具调用流程:
java复制@SpringBootTest
class CnblogsArticleToolTest {
@Autowired
private AiCodeHelperService aiService;
@Test
void testArticleSearch() {
String response = aiService.chat(
"帮我查下博客园用户BNTang的最新3篇文章");
System.out.println("AI回复:\n" + response);
// 验证响应包含预期内容
assertTrue(response.contains("标题"));
assertTrue(response.contains("阅读数"));
}
}
调试技巧:
工具描述(@Tool注解的value值)直接影响AI的调用准确性。好的描述应包含:
java复制@Tool(name = "cnblogsSearch", value = """
从博客园获取最新文章。输入可以是:
- 博客园用户名(例如:'someUser')
- 完整的个人主页URL(例如:'https://www.cnblogs.com/someUser/')
可选择性地附加'|N'来限制结果数量,例如:'someUser|5'。
返回包含标题、链接、日期、摘要、阅读数、评论数、推荐数的JSON数组。
""")
确保工具在各种异常情况下都能妥善处理:
网络问题:
输入错误:
页面结构变化:
java复制private Document fetchDocumentWithRetries(String url, int maxAttempts, int timeoutMs) {
int attempt = 0;
while (attempt < maxAttempts) {
try {
return Jsoup.connect(url)
.userAgent("Mozilla/5.0")
.timeout(timeoutMs)
.get();
} catch (IOException e) {
attempt++;
log.warn("Attempt {} failed for {}", attempt, url);
if (attempt < maxAttempts) {
try {
Thread.sleep(1000 * attempt); // 退避等待
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
return null;
}
缓存策略:
并行处理:
资源控制:
工具调用技术不限于网页抓取,还可以实现:
数据查询:
文件操作:
系统集成:
多媒体处理:
例如,我们可以轻松扩展一个数据库查询工具:
java复制@Tool("查询用户信息工具")
public String queryUserInfo(
@P("用户ID,多个ID用逗号分隔") String userIds) {
List<User> users = userRepository.findByIdIn(
Arrays.stream(userIds.split(","))
.map(String::trim)
.collect(Collectors.toList()));
return toJson(users);
}
在实际开发中,可能会遇到以下典型问题:
工具未被调用:
参数传递错误:
结果解析失败:
性能瓶颈:
反爬虫限制:
通过这个项目,我总结了工具调用设计的几个关键原则:
工具调用技术正在重塑AI应用开发模式。它让AI系统不再受限于模型训练数据,而是可以通过工具集成实时获取外部知识、执行复杂操作。这种"大脑+四肢"的架构,正是构建真正智能系统的关键。