Tối ưu hóa độ trễ và chi phí Intent Routing với Semantic Router: Hướng dẫn định tuyến AI dựa trên Vector

AI tutorial - IT technology blog
AI tutorial - IT technology blog

Vấn đề: Độ trễ và chi phí tiềm ẩn của Intent Routing bằng LLM

Mỗi AI agent hội thoại đều đối mặt với một ngã rẽ quyết định ngay khi người dùng gửi prompt: Bước tiếp theo cần làm gì? Truy vấn này cần thực hiện một truy vấn SQL, tìm kiếm vector trong tài liệu tài liệu, chạy code Python trong sandbox, hay chỉ là một câu chào hỏi soạn sẵn?

Phần lớn các đội ngũ giải quyết vấn đề này bằng cách đưa prompt vào một model gọn nhẹ như GPT-4o-mini hoặc Claude 3.5 Haiku. LLM sẽ phân tích văn bản, trích xuất nhãn ý định (intent) và trả về một payload JSON.

Cách làm này vẫn chạy tốt, nhưng nó tạo ra hai nút thắt cổ chai nghiêm trọng trong môi trường production:

  • Độ trễ tê liệt: Ngay cả các hosted model tốc độ cao cũng mất từ 300ms đến 800ms chỉ để trả về một token định tuyến duy nhất. Sự chậm trễ này xảy ra trước cả khi logic truy xuất (retrieval) hoặc agent cốt lõi của bạn bắt đầu chạy.
  • Chi phí API dồn tích theo cấp số nhân: Chạy 200.000 truy vấn mỗi ngày qua một bộ phân loại LLM sẽ đốt tiền rất nhanh. Với khoảng ~150 prompt token mỗi request, việc phân loại những câu chào cơ bản như "Xin chào" hay "Hóa đơn của tôi ở đâu?" đã ngốn hơn 180 USD mỗi tháng trên các mini model. Đối với các model cao cấp như GPT-4o, hóa đơn đó có thể vọt lên hơn 1.500 USD.

Việc sinh văn bản chỉ để đưa ra một quyết định định tuyến nhị phân là hoàn toàn không cần thiết. Bạn không cần một bộ giải mã tự hồi quy (autoregressive decoder) hàng chục tỷ tham số chỉ để chọn một nhánh rẽ. Thứ bạn thực sự cần chỉ là độ tương đồng hình học (geometric similarity).

Semantic Router thay thế toàn bộ lệnh gọi LLM đó bằng các phép toán vector. Bằng cách so sánh embedding của truy vấn với các cụm route định sẵn thông qua cosine similarity, ứng dụng của bạn có thể đưa ra quyết định định tuyến cục bộ chỉ trong 5ms đến 20ms—hoàn toàn không chạm tới quota API bên ngoài.

Bắt đầu nhanh (5 phút): Cài đặt và cấu hình Semantic Router

Hãy cùng xây dựng một bản prototype hoàn chỉnh bằng Python. Chúng ta sẽ phân loại các yêu cầu gửi đến của người dùng vào ba nhánh riêng biệt: thanh toán (billing), hỗ trợ kỹ thuật (tech support) và trò chuyện thông thường (chitchat).

1. Cài đặt các thư viện phụ thuộc

Đầu tiên, hãy cài đặt package semantic-router cùng với sentence-transformers. Chúng ta sẽ dùng một embedding model cục bộ nhỏ gọn để việc định tuyến chạy hoàn toàn trong bộ nhớ bên trong container ứng dụng.

pip install semantic-router sentence-transformers

2. Định nghĩa các Route và câu mẫu (Utterances)

Tạo một script có tên router_demo.py. Chúng ta định nghĩa từng route cùng một vài câu mẫu tiêu biểu nhằm nắm bắt chính xác ý định của người dùng:

from semantic_router import Route
from semantic_router.encoders import HuggingFaceEncoder
from semantic_router.layer import RouteLayer

# Định nghĩa các route bằng cách cung cấp các câu mẫu
billing_route = Route(
    name="billing",
    utterances=[
        "Tôi cần hóa đơn mới nhất của mình",
        "Bạn có thể cập nhật thẻ tín dụng đã lưu của tôi không?",
        "Tại sao tôi lại bị tính phí hai lần trong tháng này?",
        "Hủy gói đăng ký của tôi ngay lập tức",
        "Tôi có thể tải biên lai thanh toán ở đâu?"
    ]
)

chitchat_route = Route(
    name="chitchat",
    utterances=[
        "Xin chào bạn!",
        "Hôm nay bạn thế nào?",
        "Kể cho tôi một câu chuyện cười đi",
        "Chào buổi sáng bot",
        "Ai đã tạo ra bạn?"
    ]
)

tech_support_route = Route(
    name="tech_support",
    utterances=[
        "API trả về mã lỗi 500",
        "Payload của webhook xác minh không thành công",
        "Docker container bị crash khi khởi động",
        "Kết nối cơ sở dữ liệu bị timed out",
        "Tôi liên tục nhận lỗi unauthorized từ endpoint"
    ]
)

routes = [billing_route, chitchat_route, tech_support_route]

3. Khởi tạo Route Layer

Tiếp theo, hãy tải encoder và khởi tạo RouteLayer. Chúng ta sẽ dùng all-MiniLM-L6-v2, một model 80MB chạy mượt mà ngay trên phần cứng CPU cơ bản:

# Sử dụng mô hình embedding cục bộ gọn nhẹ và nhanh chóng
encoder = HuggingFaceEncoder(name="sentence-transformers/all-MiniLM-L6-v2")

# Xây dựng routing layer
router = RouteLayer(encoder=encoder, routes=routes)

# Thử nghiệm với các truy vấn
queries = [
    "Tôi có thể lấy file PDF thanh toán tuần trước không?",
    "Server của tôi từ chối kết nối tới cổng 5432",
    "Chào bạn, có chuyện gì thế?",
    "Sải cánh trung bình của đại bàng là bao nhiêu?"
]

for query in queries:
    decision = router(query)
    print(f"Truy vấn: '{query}' -> Route: {decision.name}")

Khi chạy script, kết quả phân loại sẽ xuất hiện ngay tức thì với tính xác định tuyệt đối:

Truy vấn: 'Tôi có thể lấy file PDF thanh toán tuần trước không?' -> Route: billing
Truy vấn: 'Server của tôi từ chối kết nối tới cổng 5432' -> Route: tech_support
Truy vấn: 'Chào bạn, có chuyện gì thế?' -> Route: chitchat
Truy vấn: 'Sải cánh trung bình của đại bàng là bao nhiêu?' -> Route: None

Truy vấn về đại bàng trả về kết quả None. Do câu hỏi này không đạt ngưỡng tương đồng đối với cả ba route, router đã đánh dấu an toàn để chuyển sang cơ chế fallback cho câu hỏi mở.

Phân tích chuyên sâu: Cách Vector Embeddings định tuyến Intent mà không cần LLM

Hiểu rõ cơ chế vận hành bên dưới sẽ giúp bạn dễ dàng tinh chỉnh ngưỡng tin cậy (confidence threshold) và mở rộng quy mô routing layer của mình.

Cơ chế hình học

Semantic Router bỏ qua hoàn toàn quy trình sinh văn bản (text-generation). Thay vào đó, nó dựa vào không gian biểu diễn dày đặc (dense representation spaces). Dưới đây là hành trình xử lý của một request:

  1. Tính toán trước (Pre-computation): Khi ứng dụng khởi động, router mã hóa toàn bộ các câu mẫu thành các vector embedding 384 chiều và lưu vào bộ nhớ cache.
  2. Vector hóa truy vấn (Query Vectorization): Khi tin nhắn người dùng đến, model cục bộ sẽ ánh xạ văn bản vào chính không gian vector đó. Trên một cloud instance 2-core tiêu chuẩn, bước này chỉ mất từ 4ms đến 12ms.
  3. Tính điểm tương đồng (Similarity Scoring): Engine sẽ tính toán cosine similarity giữa vector truy vấn gửi đến và từng vector route đã được cache.
  4. Lọc theo ngưỡng (Threshold Filtering): Nếu kết quả khớp cao nhất vượt qua ngưỡng điểm của route (ví dụ: 0.75), router sẽ trả về tên route đó ngay lập tức. Nếu tất cả các điểm đều không đạt ngưỡng, nó sẽ mặc định trả về None.

Bộ phân loại LLM vs. Semantic Router

Đặt hai phương pháp cạnh nhau sẽ thấy rõ lý do vì sao định tuyến vector đang nhanh chóng thay thế các prompt phân loại bằng LLM:

  • Độ trễ: Phân loại bằng prompt LLM mất trung bình từ 350ms–800ms. Semantic Router chỉ mất từ 5ms–20ms.
  • Chi phí vận hành: Định tuyến bằng LLM tiêu tốn từ 0,15 USD đến 5,00 USD cho mỗi triệu token. Semantic Router hoàn toàn miễn phí (0 USD phí API) vì nó chạy cục bộ ngay trong tiến trình bộ nhớ.
  • Tính tất định: LLM dễ gặp lỗi trôi lệch schema (schema drift), định dạng thay đổi bất ngờ và lỗi parse JSON. Các phép tính khoảng cách vector mang tính tất định tuyệt đối về mặt toán học.
  • Thông lượng (Throughput): Một cloud container giá 10 USD/tháng (2 vCPU, 4GB RAM) có thể dễ dàng xử lý hơn 1.200 lượt đánh giá định tuyến mỗi giây mà không lo chạm giới hạn rate limit.

Sử dụng nâng cao: Ngưỡng động và Định tuyến lai (Hybrid Routing)

Hệ thống chạy production đòi hỏi khả năng kiểm soát chi tiết. Các tác vụ có rủi ro cao cần tiêu chí khớp khắt khe, trong khi các nhánh trò chuyện thông thường có thể nới lỏng biên độ.

1. Tinh chỉnh độ nhạy của Route

Các hành động nhạy cảm (như xóa tài khoản hay yêu cầu hoàn tiền) đòi hỏi ngưỡng tin cậy cao hơn để loại bỏ cảnh báo sai (false positive). Ngược lại, các thắc mắc chung sẽ hoạt động hiệu quả hơn với rào cản thấp hơn. Bạn có thể cấu hình độ nhạy trực tiếp trên từng route:

from semantic_router import Route

refund_route = Route(
    name="critical_refund",
    utterances=[
        "Tôi muốn được hoàn tiền toàn bộ qua chargeback",
        "Hoàn tiền đơn hàng của tôi ngay lập tức",
        "Tôi cần nhận lại tiền cho hóa đơn này"
    ],
    score_threshold=0.82  # Nghiêm ngặt: tránh kích hoạt nhầm
)

faq_route = Route(
    name="general_faq",
    utterances=[
        "Giờ làm việc của bên bạn là khi nào?",
        "Văn phòng của bạn ở đâu?",
        "Làm thế nào để tôi liên hệ với bộ phận kinh doanh?"
    ],
    score_threshold=0.68  # Linh hoạt: bắt được nhiều biến thể hơn
)

2. Xây dựng Agent Dispatcher có cơ chế Fallback

Dưới đây là một dispatcher hoàn chỉnh giúp xử lý các tác vụ có cấu trúc ngay tại chỗ và chỉ dành các lệnh gọi LLM tốn kém cho những truy vấn thực sự mơ hồ:

import time
from semantic_router.layer import RouteLayer
from semantic_router.encoders import HuggingFaceEncoder

class IntelligentAgentDispatcher:
    def __init__(self, routes):
        self.encoder = HuggingFaceEncoder(name="sentence-transformers/all-MiniLM-L6-v2")
        self.router = RouteLayer(encoder=self.encoder, routes=routes)
        
    def dispatch(self, user_prompt: str):
        start_time = time.perf_counter()
        match = self.router(user_prompt)
        latency_ms = (time.perf_counter() - start_time) * 1000
        
        if match.name == "billing":
            return self.handle_billing(user_prompt, latency_ms)
        elif match.name == "tech_support":
            return self.handle_support_ticket(user_prompt, latency_ms)
        elif match.name == "chitchat":
            return {"handler": "static_cache", "response": "Xin chào! Tôi có thể giúp gì cho bạn?", "latency": f"{latency_ms:.2f}ms"}
        else:
            return self.delegate_to_llm(user_prompt, latency_ms)
            
    def handle_billing(self, prompt, latency):
        return {"handler": "sql_billing_service", "action": "query_invoices", "latency": f"{latency:.2f}ms"}

    def handle_support_ticket(self, prompt, latency):
        return {"handler": "jira_integration", "action": "open_ticket", "latency": f"{latency:.2f}ms"}

    def delegate_to_llm(self, prompt, latency):
        # Chỉ chuyển tiếp sang bước sinh tốn kém của LLM khi semantic route không khớp
        return {"handler": "heavy_llm_agent", "action": "generate_response", "routing_latency": f"{latency:.2f}ms"}

Lời khuyên thực tế: Bài học từ các đợt triển khai thực tế

Vận hành bộ định tuyến dựa trên vector trên môi trường production mang lại lợi ích rõ rệt. Qua nhiều dự án triển khai có lưu lượng truy cập cao, việc loại bỏ phân loại bằng prompt đã giúp giảm trung bình 420ms trên thời gian phản hồi p95. Đồng thời, nó cũng tiết kiệm khoảng 240 USD chi phí token phân loại hàng tháng cho mỗi bot.

Hãy ghi nhớ những nguyên tắc vận hành sau khi triển khai Semantic Router:

  • Giữ từ 10 đến 15 câu mẫu cho mỗi route: Không phải cứ nhiều hơn là tốt hơn. Thêm hàng trăm câu mẫu sẽ kéo dài thời gian khởi tạo và có thể làm loãng ranh giới giữa các cụm. Hãy tập trung vào những cách diễn đạt riêng biệt, mang tín hiệu rõ ràng.
  • Chọn embedding model phù hợp: Với dữ liệu tiếng Anh, sentence-transformers/all-MiniLM-L6-v2 mang lại sự cân bằng lý tưởng giữa tốc độ (dưới 10ms trên CPU) và độ chính xác. Nếu cần hỗ trợ đa ngôn ngữ, hãy cân nhắc BAAI/bge-m3 hoặc paraphrase-multilingual-MiniLM-L12-v2.
  • Theo dõi có hệ thống các truy vấn chưa định tuyến được: Xây dựng một dashboard đơn giản để theo dõi các truy vấn trả về None. Nhật ký này sẽ giúp phát hiện các ý định mới của người dùng và các điểm mù từ vựng trong định nghĩa route hiện tại.
  • Cân chỉnh ngưỡng trên môi trường staging: Bắt đầu với ngưỡng cơ sở là 0.70. Nếu log staging xuất hiện trường hợp bắt nhầm (false positive), hãy tăng dần ngưỡng thêm 0.03 mỗi lần cho đến khi kết quả khớp đạt độ chính xác và tin cậy cao.
Share: