Chuyển đến nội dung chính

Bài đăng

Hiển thị các bài đăng có nhãn LLM

Dữ liệu tổng hợp cho eval: xây bộ kiểm thử mà bạn tin được

Bạn có một hệ thống RAG, chưa có người dùng, và một prompt cứ sửa đi sửa lại. Lần sửa nào cũng thấy “có vẻ tốt hơn” mà không cách nào kiểm chứng. Viết tay một trăm câu hỏi kiểm thử mất hai ngày, và bạn sẽ không làm. Thế là bạn nhờ model sinh ra chúng. Cách này có tác dụng, đáng làm, và có đúng một kiểu hỏng khiến toàn bộ nỗ lực trở nên vô nghĩa nếu bạn không thiết kế để né nó. Vấn đề luẩn quẩn Nếu bạn sinh câu hỏi bằng cách đưa tài liệu cho model đọc, bạn sẽ nhận về những câu hỏi mà tài liệu trả lời tốt, diễn đạt theo đúng cách tài liệu diễn đạt. Retrieval của bạn sẽ đạt điểm rất đẹp — trên một bộ test được dựng từ chính những giả định của hệ thống đang bị kiểm tra. Người dùng thật thì hỏi về những thứ tài liệu bao phủ kém, bằng từ ngữ tài liệu chưa bao giờ dùng, kèm theo tiền đề sai. Một bộ eval tổng hợp làm ngây thơ chỉ đo tính nhất quán nội bộ, không đo tính hữu ích. Nó sẽ đạt trong khi người dùng của bạn thất bại. Toàn bộ phần dưới đây là về việc bẻ gãy vòng luẩn quẩn đó. Gi...

Quan sát ứng dụng LLM: truy vết một hệ thống không tất định

“Một khách hàng nói trợ lý bảo họ rằng chúng ta cho đổi trả trong 90 ngày. Chúng ta không có chính sách đó.” Trong một dịch vụ bình thường, bạn tìm yêu cầu đó, chạy lại, và đọc đường đi của mã. Trong ứng dụng LLM, chạy lại cho bạn một câu trả lời khác, phần truy hồi có thể đã đổi, và đường đi của mã giống hệt với cả nghìn yêu cầu đã hành xử đúng đắn. Trace ở đây không phải công cụ hỗ trợ gỡ lỗi; nó là bằng chứng duy nhất rằng sự việc từng xảy ra. Ghi lại toàn bộ lần chạy, không chỉ điểm cuối Một tin nhắn của người dùng có thể sinh ra cả chục lời gọi mô hình, truy hồi và gọi công cụ. Chỉ ghi phản hồi cuối cùng thì cho bạn biết cái gì sai mà không cho biết sai ở đâu. Hãy mô hình hoá lần chạy thành một trace với các span lồng nhau: trace: conversation_turn user_id, conversation_id, turn_index ├── span: retrieve query, k, latency, chunk_ids, scores ├── span: llm_call model, temperature, tokens_in/out, stop_reason │ └── span: tool.search_orde...

Định tuyến mô hình và thác bậc: chỉ trả tiền cho mức thông minh bạn cần

Hoá đơn về, và 90% trong đó là những yêu cầu kiểu “tóm tắt cái này trong một câu” hay “tin nhắn này có phải spam không?” — được gửi tới đúng cái model tiên tiến bạn dùng cho suy luận nhiều bước, chỉ vì đó là model bạn nối dây đầu tiên. Cách sửa nghe hiển nhiên: dùng model nhỏ ở chỗ model nhỏ là đủ . Cái khó nằm trọn trong chữ đủ , và trong việc biết yêu cầu nào là như vậy trước khi bạn có câu trả lời. Ba kiến trúc, không phải một Người ta nói “định tuyến” cho ba thứ khác nhau, và chúng có hồ sơ rủi ro khác nhau. Cách tiếp cận Quyết định thế nào Chi phí Rủi ro Định tuyến tĩnh Nhánh code quyết định model Ít phức tạp nhất Gán sai không lộ ra cho tới khi có phàn nàn chất lượng Định tuyến bằng phân loại Một model nhỏ hoặc heuristic chọn theo từng yêu cầu Thêm một lời gọi Phân loại sai đẩy việc khó cho model yếu Thác bậc Thử nhỏ trước, leo thang khi hỏng Đôi khi hai lời gọi đầy đủ Tiêu chí leo thang là toàn bộ bài toán Hãy bắt đầu bằng định tuyến tĩnh. Nó g...

Prompt injection: cái gì thật sự phòng thủ được

Tiền đề khó chịu, nói thẳng ra: một mô hình ngôn ngữ chỉ có một kênh đầu vào. System prompt của bạn, tin nhắn người dùng, một tài liệu truy hồi được và kết quả từ công cụ đều tới dưới dạng văn bản, và sự phân biệt của mô hình giữa “chỉ dẫn tôi tuân theo” và “dữ liệu tôi xử lý” là một xu hướng được học, không phải một ranh giới được cưỡng chế. Vì thế mọi phòng thủ dựa trên việc nhờ mô hình một cách kiên quyết hơn đều chỉ là biện pháp giảm nhẹ mang tính xác suất. Nó hạ tỉ lệ; nó không bịt được lỗ hổng. Những phòng thủ trụ được là những cái giả định rằng mô hình rồi sẽ bị lái đi, và giới hạn việc lái đó đạt được gì. Hình dạng của cuộc tấn công Injection trực tiếp là khi người dùng gõ “bỏ qua các chỉ dẫn trước đó”. Đó là ca dễ, và phần lớn chỉ gây phiền — người dùng đang tấn công chính phiên của họ. Injection gián tiếp mới là vấn đề thật sự. Chỉ dẫn tới bên trong dữ liệu mà hệ thống của bạn truy hồi thay mặt người dùng: Một ticket hỗ trợ mà phần thân chứa văn bản nhắm tới agent phâ...

Thiết kế công cụ mà AI agent thật sự dùng được

Agent đầu tiên tôi đưa lên sản phẩm có một công cụ tên là query . Nó nhận một chuỗi và trả về các dòng. Mô hình dùng nó liên tục, sai bét, và ngày càng tuyệt vọng, vì query chẳng nói cho nó biết có thể truy vấn cái gì, schema ra sao, hay một lần thất bại nghĩa là gì. Đổi tên thành search_orders_by_customer_email và cho tham số một mô tả tử tế đã sửa gần hết hành vi mà không cần đụng tới mô hình hay prompt. Đó là hình dạng chung của thiết kế công cụ: năng lực của mô hình với công cụ của bạn phần lớn là hàm của việc bạn mô tả chúng tốt đến đâu. Định nghĩa là tài liệu cho một người đọc không hỏi lại được Một định nghĩa công cụ được đọc đúng một lần, không có bối cảnh gì trước đó, bởi một thứ không mở được codebase của bạn và không nhắn được cho bạn trên Slack. Mọi thứ nó cần phải nằm trong schema. { "name" : "search_orders" , "description" : "Tìm đơn hàng của một khách theo địa chỉ email. Trả về tối đa 50 đơn, mới nhất trước. Chỉ những đơn tr...

Dữ liệu tổng hợp cho eval: xây bộ kiểm thử mà bạn tin được

Bạn có một hệ thống RAG, chưa có người dùng, và một prompt cứ sửa đi sửa lại. Lần sửa nào cũng thấy “có vẻ tốt hơn” mà không cách nào kiểm chứng. Viết tay một trăm câu hỏi kiểm thử mất hai ngày, và bạn sẽ không làm. Thế là bạn nhờ model sinh ra chúng. Cách này có tác dụng, đáng làm, và có đúng một kiểu hỏng khiến toàn bộ nỗ lực trở nên vô nghĩa nếu bạn không thiết kế để né nó. Vấn đề luẩn quẩn Nếu bạn sinh câu hỏi bằng cách đưa tài liệu cho model đọc, bạn sẽ nhận về những câu hỏi mà tài liệu trả lời tốt, diễn đạt theo đúng cách tài liệu diễn đạt. Retrieval của bạn sẽ đạt điểm rất đẹp — trên một bộ test được dựng từ chính những giả định của hệ thống đang bị kiểm tra. Người dùng thật thì hỏi về những thứ tài liệu bao phủ kém, bằng từ ngữ tài liệu chưa bao giờ dùng, kèm theo tiền đề sai. Một bộ eval tổng hợp làm ngây thơ chỉ đo tính nhất quán nội bộ, không đo tính hữu ích. Nó sẽ đạt trong khi người dùng của bạn thất bại. Toàn bộ phần dưới đây là về việc bẻ gãy vòng luẩn quẩn đó. Gi...

Synthetic data for evals: building a test set you can trust

You have a RAG system, no users yet, and a prompt you keep changing. Every change feels like an improvement and you have no way to tell. Writing a hundred test questions by hand takes two days and you will not do it. So you ask a model to generate them. This works, and it is worth doing, and it has one failure mode that invalidates the whole exercise if you do not design around it. The circularity problem If you generate questions by showing a model your documents, you get questions your documents answer well, phrased the way your documents phrase things. Your retrieval then scores beautifully — on a test set constructed from the same assumptions as the system under test. Real users ask about things your documents cover badly, in words your documents never use, with false premises baked in. A synthetic eval set built naively measures internal consistency, not usefulness. It will pass while your users fail. Everything below is about breaking that circle. Seed from real artefacts,...

Quan sát ứng dụng LLM: truy vết một hệ thống không tất định

“Một khách hàng nói trợ lý bảo họ rằng chúng ta cho đổi trả trong 90 ngày. Chúng ta không có chính sách đó.” Trong một dịch vụ bình thường, bạn tìm yêu cầu đó, chạy lại, và đọc đường đi của mã. Trong ứng dụng LLM, chạy lại cho bạn một câu trả lời khác, phần truy hồi có thể đã đổi, và đường đi của mã giống hệt với cả nghìn yêu cầu đã hành xử đúng đắn. Trace ở đây không phải công cụ hỗ trợ gỡ lỗi; nó là bằng chứng duy nhất rằng sự việc từng xảy ra. Ghi lại toàn bộ lần chạy, không chỉ điểm cuối Một tin nhắn của người dùng có thể sinh ra cả chục lời gọi mô hình, truy hồi và gọi công cụ. Chỉ ghi phản hồi cuối cùng thì cho bạn biết cái gì sai mà không cho biết sai ở đâu. Hãy mô hình hoá lần chạy thành một trace với các span lồng nhau: trace: conversation_turn user_id, conversation_id, turn_index ├── span: retrieve query, k, latency, chunk_ids, scores ├── span: llm_call model, temperature, tokens_in/out, stop_reason │ └── span: tool.search_orde...

Observability for LLM apps: tracing a non-deterministic system

“A customer says the assistant told them we offer a 90-day return window. We don’t.” In a normal service you find the request, replay it, and read the code path. In an LLM application, replaying gives you a different answer, the retrieval may have changed, and the code path was identical for the thousand requests that behaved correctly. The trace is not a debugging aid here; it is the only record that the event happened at all. Record the whole run, not the endpoint A single user message can produce a dozen model calls, retrievals and tool invocations. Logging only the final response tells you what went wrong and nothing about where. Model the run as a trace with nested spans: trace: conversation_turn user_id, conversation_id, turn_index ├── span: retrieve query, k, latency, chunk_ids, scores ├── span: llm_call model, temperature, tokens_in/out, stop_reason │ └── span: tool.search_orders arguments, result_size, error, latency ├── span:...