Spring AI: Dynamic Tool Calling

- Published on
- /9 mins read/
Khi xây dựng Enterprise AI Agent với hàng chục hoặc hàng trăm công cụ nghiệp vụ, việc nạp tĩnh toàn bộ JSON Schema vào mỗi lượt gọi LLM sẽ làm bùng nổ chi phí token, kéo dài độ trễ và gia tăng tỷ lệ ảo giác chọn nhầm công cụ. Giải pháp nằm ở kiến trúc Dynamic Tool Calling kết hợp Vector Store và Advisor Pattern trong Spring AI.
# bài toán quá tải Context Window và kinh tế học Token
Trong các hệ sinh thái doanh nghiệp kết nối nhiều hệ thống nghiệp vụ (ERP, CRM, Core Banking, Kho hàng, MCP Servers), số lượng hàm công cụ (Tools / Function Calling) có thể dễ dàng chạm mốc từ 50 đến hơn 200 tools:
# công thức chi phí token overhead
Mỗi định nghĩa công cụ gửi tới LLM bao gồm: tên hàm, chuỗi mô tả ngữ nghĩa (description), các thuộc tính tham số (properties), kiểu dữ liệu (types), và danh sách trường bắt buộc (required). Trung bình một schema công cụ tiêu tốn T_schema ≈ 200 - 350 tokens.
Nếu hệ thống có M = 100 công cụ và nhận 500,000 lượt yêu cầu trò chuyện mỗi tháng:
Token dư thừa mỗi request = M × T_schema = 100 × 250 = 25,000 tokens
Tổng token lãng phí hàng tháng = 500,000 × 25,000 = 12.5 Tỷ tokens
Với mức giá mô hình flagship trung bình 2.5 / 1M input tokens, doanh nghiệp phải trả **31,250 mỗi tháng** chỉ để mô tả các công cụ mà phần lớn không bao giờ được dùng đến trong request đó.
# ba hệ quả kỹ thuật nghiêm trọng
- Lãng phí chi phí vận hành: Hơn 90% chi phí đầu vào là để truyền tải các schema không liên quan.
- Kéo dài Time-to-First-Token (TTFT): LLM phải tính toán self-attention trên một ma trận ngữ cảnh hàng chục nghìn tokens trước khi sinh ra token đầu tiên, làm tăng độ trễ mạng thêm 2 đến 4 giây.
- Hiện tượng ảo giác công cụ (Tool Hallucination & Confusion): Khi có nhiều công cụ có chữ ký tương đồng (ví dụ:
cancelBooking,cancelOrder,cancelTicket), việc xuất hiện cùng lúc trong prompt khiến mô hình dễ kích hoạt sai công cụ hoặc điền nhầm tham số.
# kiến trúc Dynamic Tool Calling với Spring AI Advisor
Để giải quyết bài toán trên, chúng ta áp dụng mô hình RAG cho Tool Schemas. Thay vì đưa toàn bộ công cụ vào ChatClient, hệ thống chỉ truy vấn và nạp động Top-K (K = 3 - 5) công cụ có độ tương đồng ngữ nghĩa cao nhất với câu hỏi của người dùng.
Trong Spring AI 1.x, cơ chế sạch nhất để can thiệp vào vòng đời của request mà không làm xáo trộn mã nguồn nghiệp vụ là xây dựng một CallAroundAdvisor.
# hiện thực hóa mã nguồn kỹ thuật
# 1. tự động lập chỉ mục các Bean có gắn @Tool
Khi ứng dụng khởi động, hệ thống tự động quét toàn bộ Spring Beans, bóc tách metadata và nạp vào VectorStore:
package com.tungdadev.agent.tools;
import org.springframework.ai.document.Document;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class DynamicToolRegistry {
private final ApplicationContext applicationContext;
private final VectorStore vectorStore;
private final Map<String, ToolCallback> callbackMap = new ConcurrentHashMap<>();
public DynamicToolRegistry(ApplicationContext applicationContext, VectorStore vectorStore) {
this.applicationContext = applicationContext;
this.vectorStore = vectorStore;
}
@EventListener(ApplicationReadyEvent.class)
public void indexAllTools() {
// Tìm toàn bộ beans có chứa phương thức gắn annotation @Tool
String[] beanNames = applicationContext.getBeanDefinitionNames();
List<Document> toolDocuments = new ArrayList<>();
for (String beanName : beanNames) {
Object bean = applicationContext.getBean(beanName);
ToolCallback[] callbacks = ToolCallbacks.from(bean);
for (ToolCallback callback : callbacks) {
String toolName = callback.getToolDefinition().name();
String description = callback.getToolDefinition().description();
callbackMap.put(toolName, callback);
// Lưu nội dung mô tả vào Vector Store để phục vụ tìm kiếm ngữ nghĩa
Document doc = new Document(
description,
Map.of(
"toolName", toolName,
"type", "agent_tool"
)
);
toolDocuments.add(doc);
}
}
if (!toolDocuments.isEmpty()) {
vectorStore.add(toolDocuments);
}
}
public ToolCallback getCallback(String toolName) {
return callbackMap.get(toolName);
}
}# 2. thiết kế CallAroundAdvisor nạp công cụ động
Advisor này đóng vai trò chặn bắt (interceptor) trước khi request tới LLM, thực hiện tìm kiếm tương đồng và tiêm các ToolCallback tương ứng vào cấu hình yêu cầu:
package com.tungdadev.agent.advisor;
import com.tungdadev.agent.tools.DynamicToolRegistry;
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain;
import org.springframework.ai.document.Document;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.Ordered;
import java.util.*;
public class DynamicToolSelectionAdvisor implements CallAroundAdvisor {
private final VectorStore vectorStore;
private final DynamicToolRegistry toolRegistry;
private final int topK;
private final double similarityThreshold;
private final List<ToolCallback> alwaysOnTools;
public DynamicToolSelectionAdvisor(VectorStore vectorStore,
DynamicToolRegistry toolRegistry,
int topK,
double similarityThreshold,
List<ToolCallback> alwaysOnTools) {
this.vectorStore = vectorStore;
this.toolRegistry = toolRegistry;
this.topK = topK;
this.similarityThreshold = similarityThreshold;
this.alwaysOnTools = alwaysOnTools != null ? alwaysOnTools : List.of();
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
String userPrompt = advisedRequest.userText();
// 1. Tìm kiếm ngữ nghĩa trong Vector Store
SearchRequest searchRequest = SearchRequest.query(userPrompt)
.withTopK(topK)
.withSimilarityThreshold(similarityThreshold);
List<Document> matchedDocs = vectorStore.similaritySearch(searchRequest);
// 2. Chuyển đổi tên tool thành danh sách ToolCallback thực thi
Set<ToolCallback> selectedTools = new HashSet<>(alwaysOnTools);
for (Document doc : matchedDocs) {
String toolName = (String) doc.getMetadata().get("toolName");
ToolCallback callback = toolRegistry.getCallback(toolName);
if (callback != null) {
selectedTools.add(callback);
}
}
// 3. Tái cấu trúc AdvisedRequest với danh sách tools đã được chọn lọc
AdvisedRequest modifiedRequest = AdvisedRequest.from(advisedRequest)
.toolCallbacks(selectedTools.toArray(new ToolCallback[0]))
.build();
// 4. Tiếp tục chuỗi xử lý
return chain.nextAroundCall(modifiedRequest);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 100;
}
@Override
public String getName() {
return "DynamicToolSelectionAdvisor";
}
}# 3. cấu hình ChatClient trong Spring Configuration
Đăng ký Advisor vào ChatClient.Builder để toàn bộ các lời gọi agent đều tự động thừa hưởng cơ chế truy xuất động:
package com.tungdadev.agent.config;
import com.tungdadev.agent.advisor.DynamicToolSelectionAdvisor;
import com.tungdadev.agent.tools.DynamicToolRegistry;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class AgentChatConfiguration {
@Bean
public ChatClient enterpriseAgentChatClient(ChatClient.Builder builder,
VectorStore vectorStore,
DynamicToolRegistry toolRegistry) {
return builder
.defaultAdvisors(
new DynamicToolSelectionAdvisor(
vectorStore,
toolRegistry,
3, // Top-3 tools liên quan nhất
0.72, // Ngưỡng Cosine Similarity tối thiểu
List.of() // Always-on tools nếu có
)
)
.build();
}
}# chiến lược kết hợp: Hybrid Tool Retrieval và RBAC
Trong môi trường doanh nghiệp thực tế, chỉ tìm kiếm ngữ nghĩa đơn thuần là chưa đủ. Bạn cần thiết lập hai cơ chế bảo vệ:
- Always-On Tools: Một số công cụ mang tính kiểm tra cốt lõi (như ghi nhật ký kiểm toán, kiểm tra quyền truy cập) luôn được đưa vào danh sách
alwaysOnToolsmà không phụ thuộc vào kết quả vector search. - Kiểm soát quyền truy cập theo vai trò (Role-Based Tool Filtering):
- Trước khi vector search trả về kết quả cho LLM, hệ thống lọc danh sách công cụ theo quyền của người dùng hiện tại (
Authentication / GrantedAuthorities). - Nếu nhân viên hỗ trợ cấp 1 hỏi câu lệnh hoàn tiền, công cụ
issueRefundToolsẽ tự động bị loại khỏi danh sách ngay ở tầng bộ lọc metadata, ngăn chặn hoàn toàn rủi ro vượt quyền thông qua Prompt Injection.
- Trước khi vector search trả về kết quả cho LLM, hệ thống lọc danh sách công cụ theo quyền của người dùng hiện tại (
# đo lường hiệu năng thực tế
Kiểm thử hệ thống trợ lý ảo với 60 công cụ nghiệp vụ doanh nghiệp:
| Tiêu chí kỹ thuật | Nạp tĩnh toàn bộ (Static) | Dynamic Tool Retrieval | Cải thiện thực tế |
|---|---|---|---|
| Dung lượng Input Tokens trung bình | ~15,200 tokens | ~850 tokens | 📉 Tiết kiệm ~94.4% |
| Thời gian phản hồi token đầu tiên (TTFT) | 3.12 giây | 0.78 giây | ⚡ Nhanh gấp 4 lần |
| Tỷ lệ chọn đúng công cụ (Accuracy) | 82.3% | 98.1% | 🎯 Giảm thiểu ảo giác |
| Giới hạn số lượng công cụ tối đa | Bị nghẽn ở ~80 tools | Mở rộng hàng nghìn tools | 🚀 Khả năng mở rộng cao |
Kiến trúc Dynamic Tool Calling biến các Agent xây dựng trên Spring AI từ những hệ thống nguyên khối cồng kềnh thành những tác tử phần mềm tinh gọn, phản hồi nhanh và tối ưu chi phí hạ tầng ở quy mô lớn.
Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè & đồng nghiệp nhé!
Happy coding 😎 👍🏻 🚀 🔥.
On this page
- # bài toán quá tải Context Window và kinh tế học Token
- # công thức chi phí token overhead
- # ba hệ quả kỹ thuật nghiêm trọng
- # kiến trúc Dynamic Tool Calling với Spring AI Advisor
- # hiện thực hóa mã nguồn kỹ thuật
- # 1. tự động lập chỉ mục các Bean có gắn @Tool
- # 2. thiết kế CallAroundAdvisor nạp công cụ động
- # 3. cấu hình ChatClient trong Spring Configuration
- # chiến lược kết hợp: Hybrid Tool Retrieval và RBAC
- # đo lường hiệu năng thực tế