Định tuyến và Failover trong Microsoft.Extensions.AI

Đang đọc bản tiếng Việt
Read in English →

Khi việc áp dụng AI ngày càng mở rộng, chi phí, thời gian hoạt động (uptime) và độ trễ trở thành những ràng buộc ưu tiên hàng đầu trong kiến trúc ứng dụng. Việc định tuyến giữa các mô hình hoặc nhà cung cấp (providers) là giải pháp tác động trực tiếp vào cả ba yếu tố này.

Các primitive mới trong Microsoft.Extensions.AI xử lý trực tiếp vấn đề này: định tuyến theo nội dung tin nhắn, failover khi nhà cung cấp gặp sự cố hoặc xây dựng chính sách của riêng bạn trên cùng các abstraction này, tất cả đều là các triển khai của IChatClient.

Bốn kiểu experimental mới bao phủ các kịch bản chính:

  • RoutingChatClient: lớp cơ sở, một IChatClient có nhiệm vụ lựa chọn và chuyển tiếp yêu cầu đến một client khác theo từng request.
  • SemanticRoutingChatClient: một RoutingChatClient thực hiện định tuyến theo nội dung, sử dụng so khớp embedding so với các câu mẫu do ứng dụng cung cấp.
  • FailoverChatClient: một RoutingChatClient trừu tượng bổ sung vòng lặp retry, chọn lại client khác khi một lần thử thất bại trước khi cam kết bất kỳ output nào, đóng vai trò là lớp cơ sở cho các chiến lược failover khác nhau.
  • OrderedFailoverChatClient: một FailoverChatClient cụ thể thực hiện duyệt qua danh sách các client theo thứ tự.

Sơ đồ tuần tự của một yêu cầu được định tuyến: ứng dụng gửi đến RoutingChatClient, qua chính sách lựa chọn của bạn, và đến client chat được chọn

RoutingChatClient

RoutingChatClient là một IChatClient trừu tượng gọi SelectClientAsync cho mỗi request, chuyển tiếp lời gọi đến client được trả về và lan truyền phản hồi.

Cách đơn giản nhất để sử dụng là RoutingChatClient.Create, nhận vào một callback:

var router = RoutingChatClient.Create((context, ct) =>
    new(isComplexRequest(context) ? powerfulClient : cheapClient));

Đối với các chính sách định tuyến trạng thái (stateful) hoặc nâng cao hơn, hãy kế thừa từ RoutingChatClient và ghi đè phương thức SelectClientAsync:

class MyRouter : RoutingChatClient
{
    protected override ValueTask<IChatClient> SelectClientAsync(
        RoutingContext context, CancellationToken ct)
    {
        // ...
    }
}

Mỗi lời gọi đến GetResponseAsync hoặc GetStreamingResponseAsync sẽ tạo ra một RoutingContext chứa các tin nhắn request và một bản sao của ChatOptions. Đối tượng tùy chọn của caller không bao giờ được truyền trực tiếp đến client, và các thay đổi thực hiện sau khi request bắt đầu sẽ không được ghi nhận.

Điều này chia việc định hình tùy chọn thành hai cấp độ:

  • Tùy chọn cấp request: nằm trên RoutingContext.ChatOptions. Các thay đổi duy trì xuyên suốt request, bao gồm cả bất kỳ lần thử lại nào nếu client thất bại.
  • Tùy chọn cấp route: thuộc về client, thường là một wrapper ConfigureOptionsChatClient, thực hiện clone các tùy chọn request và áp dụng giá trị riêng của nó lên trên.

SemanticRoutingChatClient

SemanticRoutingChatClient, lấy cảm hứng từ semantic router của Aurelio Labs, thực hiện định tuyến theo ý nghĩa của tin nhắn. Bạn cung cấp một bộ câu mẫu cho mỗi client, và tại runtime, tin nhắn cuối cùng của người dùng sẽ được embedding và so khớp với chúng. Client có điểm tương đồng cao nhất vượt ngưỡng sẽ được chọn; nếu không có client nào đạt yêu cầu, defaultClient sẽ được sử dụng.

var router = new SemanticRoutingChatClient(
    embeddingGenerator,
    clientProfiles: new Dictionary<IChatClient, IReadOnlyList<string>>
    {
        [codingClient] = ["write code", "fix this bug", "refactor this function"],
        [creativeClient] = ["write a story", "brainstorm names", "generate a poem"],
    },
    defaultClient: generalClient,
    scoreThreshold: 0.3f);

Embedding của profile được tạo một cách lười biếng (lazy) và lưu vào bộ nhớ đệm (cache). Yêu cầu đầu tiên sẽ thực hiện embedding toàn bộ câu mẫu trong một lần gọi batch và giữ các vector đó, do đó các yêu cầu sau chỉ cần thực hiện embedding cho tin nhắn mới và so sánh.

Các tùy chọn chính:

  • scoreThreshold: điểm tối thiểu để chọn một client trong profile.
  • topK: số lượng câu khớp nhất cho mỗi yêu cầu để xem xét khi tổng hợp điểm. Mặc định là 1.
  • scoreAggregation: Mean hoặc Sum trên top-K khớp. Ảnh hưởng đến giá trị ngưỡng hợp lệ.
  • leaveOpen: mặc định SemanticRoutingChatClient sở hữu các client và embedding generator và sẽ dispose chúng. Đặt thành true để thay đổi hành vi này.

Một điểm khởi đầu hợp lý khác là topK: 5 với tổng hợp Mean, đây là giá trị mặc định của semantic router từ Aurelio Labs. Lấy trung bình nhiều khớp ổn định hơn so với việc chỉ đặt cược vào một cái gần nhất, và Mean giữ điểm số trên thang đo -1 đến 1 tương đồng.

Lưu ý về việc định tuyến lại mỗi lượt

  • Các mô hình lập luận (reasoning models) thường trả về lý do của chúng dưới dạng artifact đặc thù của nhà cung cấp. Việc chuyển đổi nhà cung cấp giữa chừng có thể làm gián đoạn phiên hội thoại.
  • Một nhà cung cấp mới đồng nghĩa với một prompt mới mà không có cache, khiến bạn mất chi phí tính toán lại tiền tố (prefix) trên mỗi lần chuyển đổi.

Nếu một cuộc hội thoại dự định giữ nguyên một phân loại, hãy quyết định một lần và duy trì nó thay vì định tuyến lại mỗi lượt.

FailoverChatClient

FailoverChatClient mở rộng RoutingChatClient với vòng lặp retry. Khi một client được chọn thất bại trước khi output streaming được hiển thị cho caller, nó sẽ gọi lại SelectClientAsync và thử lại. Sau khi output bắt đầu luân chuyển, lỗi sẽ trở thành terminal: không có cơ chế khôi phục giữa dòng.

Vòng lặp retry failover, cộng với các trường hợp buộc phải kết thúc: output đã cam kết và hủy bỏ

Các lớp dẫn xuất triển khai SelectClientAsync để cung cấp client tiếp theo và ghi đè OnRoutingUpdateAsync để theo dõi mỗi lần thử. Callback này kích hoạt sau mỗi lần gọi client (thành công, thất bại hoặc bị hủy) với FailoverChatClientAttempt chứa:

  • Client (IChatClient): client đã được gọi
  • Duration (TimeSpan): thời gian thực hiện gọi client
  • Exception (Exception?): ngoại lệ quan sát được
  • ResponseCompleted (bool): liệu phản hồi đã hoàn thành thành công hay chưa
  • OutputCommitted (bool): liệu có cập nhật streaming nào đã đến tay caller chưa
  • TimeToFirstUpdate (TimeSpan?): thời gian đến cập nhật streaming đầu tiên

DurationTimeToFirstUpdate cho phép bạn theo dõi hiệu năng của nhà cung cấp theo thời gian: thực hiện circuit-break một nhà cung cấp chậm, hoặc ghi nhận độ trễ lịch sử.

OrderedFailoverChatClient

OrderedFailoverChatClient là triển khai FailoverChatClient sẵn sàng sử dụng. Truyền cho nó một danh sách các client đã được xếp hạng và nó sẽ duyệt qua theo thứ tự: nếu client đầu tiên thất bại, hãy thử cái thứ hai, v.v. Khi tất cả client đều thất bại, ngoại lệ cuối cùng sẽ được ném ra.

var failover = new OrderedFailoverChatClient([primaryClient, backupClient, lastResortClient]);

Xây dựng trên các primitive

Ngoài các kiểu có sẵn, các bộ định tuyến tùy chỉnh có thể quản lý trạng thái ứng dụng hoặc xử lý các cấu hình khác nhau của cùng một mô hình như các route riêng biệt.

Sticky selection (Định tuyến cố định)

Đối với các cuộc hội thoại đa lượt, ứng dụng có thể giữ route đã chọn trong trạng thái phiên (session state). Bản thân bộ định tuyến có thể được tái sử dụng qua các phiên. Đặt tên các route, truyền session ID của ứng dụng qua ChatOptions.AdditionalProperties và lưu tên đã chọn trong IDistributedCache (ví dụ: Redis).

Hạn chế

RoutingChatClient luôn đưa ra lựa chọn trước khi thực hiện bất kỳ lời gọi nào: một client, được chọn từ đầu, cho mỗi yêu cầu. Điều này đặt một số mô hình định tuyến nằm ngoài phạm vi của nó như: Model cascading, Ensemble routing hoặc Hedging.

Bắt đầu

RoutingChatClient, RoutingContext, FailoverChatClient, FailoverChatClientAttempt, OrderedFailoverChatClientSemanticRoutingChatClient đã ra mắt trong Microsoft.Extensions.AI phiên bản 10.9.0. Tất cả đều được đánh dấu [Experimental] với mã chẩn đoán MEAI001.

dotnet add package Microsoft.Extensions.AI

Chúng tôi mong nhận được phản hồi của bạn về các tính năng mới này và các hành vi xung quanh cơ chế failover, báo cáo OnRoutingUpdateAsync và các tùy chọn tổng hợp của SemanticRoutingChatClient. Nếu bạn xây dựng bất cứ thứ gì với chúng, hãy gửi issue hoặc bắt đầu thảo luận trên dotnet/extensions.

Tags: #.net#ai#csharp#architecture#routing
Đàm Quang Tuyển
Đàm Quang Tuyển

Thích xây dựng hệ thống phần mềm hiệu năng cao, viết mã sạch và chia sẻ kinh nghiệm kỹ thuật.

Lofi Chill Vibes