Ai nhồi chữ, ai vẽ đường
Trong phần này · 8 mục

Lịch sử ứng dụng LLM trong vài năm qua có thể gói lại trong hai động từ: nhồi chữ và vẽ đường.
Giai đoạn đầu, gần như toàn bộ công sức dồn vào việc nhồi chữ cho đúng. Kỹ sư gọt prompt, chèn ví dụ, quy định định dạng; pipeline RAG hì hục truy xuất tài liệu rồi nhét vào context. Bài 01 đã đi hết chặng này: từ prompt engineering, Chain-of-Thought đến RAG, câu hỏi cốt lõi vẫn chỉ là cần bày những token nào trước mặt mô hình để lần dự đoán kế tiếp hữu ích hơn. LLM khi ấy là một cỗ máy đọc hiểu thụ động: nhận một context dọn sẵn, nhả ra một câu trả lời, rồi dừng.
Cuối năm 2022, ReAct kéo LLM ra khỏi vùng an toàn ấy. Đầu ra không còn chỉ là câu trả lời cuối cùng. Mô hình được sinh Action, nhận lại Observation, rồi dựa trên cuộn lịch sử vừa nối dài để chọn bước tiếp theo. Lần đầu tiên, cỗ máy không chỉ đọc những gì người khác nhồi cho nó; nó bắt đầu cầm bút vẽ đường cho chính tiến trình đang chạy.
Nhưng như Bài 02 đã mổ xẻ, cây bút ấy vẫn là văn xuôi. Một action phải được viết thành chuỗi, cắt ra bằng regex, đối chiếu với tên công cụ rồi mới được thực thi. Chỉ cần một dấu hai chấm lệch nhịp, một tham số sai kiểu, hay một câu xin lỗi lịch sự chen ngang là giao thức đứt. Tập công cụ có thể hữu hạn, nhưng cánh cửa giữa mô hình và runtime lại mở ra toàn bộ không gian chuỗi ký tự.
Ngày 13/6/2023, OpenAI ra mắt function calling.1 Lập trình viên khai báo hàm và nhận lại tên hàm cùng tham số trong một khối JSON, thay vì dò tìm chữ Action: giữa một đoạn văn. Lần đầu tiên, lời đề xuất gọi công cụ có một kênh truyền riêng.
Kênh riêng làm output có cấu trúc đáng tin hơn, nhưng chưa bảo đảm mọi lần sinh đều khớp schema. Phải sang năm 2024, hai mảnh ghép quyết định mới xuất hiện. Về phía kiến trúc, LangGraph (tháng 1/2024) đưa chu trình, state và checkpoint thành công dân hạng nhất của agent runtime.2 Về phía mô hình, Structured Outputs (tháng 8/2024) với cờ strict: true siết việc tuân thủ schema xuống tận tầng giải mã.3
Từ đó, ba thứ lần lượt dọn nhà. Luồng điều khiển rời cuộn hội thoại để thành mã nguồn. State rời chuỗi văn bản để thành dữ liệu định kiểu. Lời gọi công cụ rời văn xuôi để đi qua một kênh riêng.

Đây mới là cú bẻ lái quan trọng nhất. Mô hình vẫn giữ vai trò suy luận; hệ thống cũng không lùi về những pipeline if-else cứng nhắc. Thứ thay đổi là chỗ đặt quyền quyết định. Mô hình vẫn được đề xuất bước kế tiếp, nhưng mất hẳn cái quyền mặc nhiên biến mọi chuỗi nó vừa sinh ra thành hành động thật.
Và cú pháp đúng không bảo chứng cho logic đúng. Một khối JSON không thiếu dấu ngoặc nào nhưng gọi delete_database trong khi nghiệp vụ cần query_database không làm hệ thống thông minh hơn. Nó chỉ giúp sai lầm lọt qua parser trơn tru hơn.
Vì vậy phải dựng một ranh giới mới, và ranh giới ấy phải nằm trong mã nguồn:
Mô hình đề xuất. Runtime phân quyền. Công cụ thực thi. State ghi nhận. Verifier kiểm duyệt. Audit trail lưu bằng chứng.
Câu này đã khép lại Bài 02. Bài này đi tiếp từ đó, và gọi tên lớp mã nguồn giữ ranh giới ấy: harness. Harness engineering không tước năng lực suy luận của mô hình. Nó chỉ không để mô hình vừa chạy trên bản đồ, vừa tự vẽ thêm những con đường không ai kiểm soát.
Harness là gì?
Function calling mở ra một kênh có cấu trúc để mô hình đề xuất gọi công cụ. Nhưng một kênh truyền thông điệp chưa phải hệ thống điều khiển. Ai quyết định mô hình được nhìn thấy gì? Ai đóng khung những hành động nó được đề xuất? Ai kiểm tra đề xuất trước khi kích hoạt? Ai giữ tiến trình sống qua một cú crash, và ai lưu bằng chứng khi sự cố xảy ra?
Lớp mã nguồn trả lời những câu hỏi ấy mang một cái tên rất mộc của ngành phần mềm: harness.
Ngoài đời, harness là bộ đai an toàn của người leo núi. Nó không tạo ra lực kéo. Nó chỉ truyền lực theo hướng đã định, và giữ mạng người leo khi trượt chân. Trong phần mềm, ý niệm này đã có từ lâu dưới tên test harness: đoạn code chuẩn bị input, gọi module cần test, thu output, đo thời gian, cô lập môi trường và phân xử đạt hay trượt.
Trong kiến trúc agent, harness không chỉ bọc một lần gọi API. Nó ôm trọn vòng đời của tiến trình mà LLM tham gia. Ranh giới định nghĩa có thể xê dịch giữa các framework, nhưng một harness thực thụ phải gánh ít nhất năm trách nhiệm:
- Quản trị context: quyết định phần nào của state được render vào ngữ cảnh ở mỗi lượt gọi.
- Đóng khung không gian hành động: khai báo tập công cụ hoặc các nhánh mà mô hình được phép đề xuất.
- Kiểm duyệt: xác thực đề xuất trước khi cho nó chạm vào công cụ thật.
- Điều phối runtime: nắm luồng điều khiển, state, timeout, retry, checkpoint và các điểm dừng.
- Khả năng kiểm toán: ghi đủ bằng chứng để quan sát, điều tra và phục hồi.

Mô hình cung cấp sức mạnh suy luận. Harness quyết định sức mạnh ấy được truyền vào đâu, bị chặn ở đâu, và để lại dấu vết gì. LLM, suy cho cùng, chỉ là một bánh răng trong harness, không bao giờ là toàn bộ agent.
Harness cũng không phải tên một thư viện. LangGraph, LlamaIndex Workflows hay Temporal chỉ là nguyên liệu. Một vòng while tự viết, nếu có tập công cụ đóng, schema khắt khe, giới hạn số bước, timeout rõ ràng và cơ chế phân quyền, vẫn là một harness tốt. Ngược lại, import một framework đồ sộ không tự động mang lại những tính chất ấy. Điều định hình kiến trúc không nằm ở logo của thư viện, mà ở những ranh giới nào thật sự được code cưỡng chế.
Năm trách nhiệm trên cuối cùng quy về một câu hỏi: sau khi mô hình nhả chữ, điều gì thật sự được phép xảy ra? Sáu mục dưới đây trả lời câu hỏi ấy từ trong ra ngoài: tập lựa chọn (I), vai trò của mô hình (II), đường đi (III), bộ nhớ (IV), thời gian và bằng chứng (V), rồi một tấm gương từ chính lõi mô hình (VI).
I. LLM-as-router và tập hành động đóng
Router là thành phần đứng ở ngã rẽ và chọn nhánh kế tiếp. Dùng LLM cho việc này là một ý tưởng hay. Một email khiếu nại lộn xộn có thể cần chuyển sang kế toán, pháp chế hay hỗ trợ kỹ thuật; mô hình ngôn ngữ nắm ý định tốt hơn hàng chục khối if-else dò từ khóa.
Sự ngây thơ của agent đời đầu không nằm ở việc giao quyền chọn cho mô hình. Nó nằm ở không gian mà mô hình được chọn trong đó.
Về mặt hình thức, một router ánh xạ đầu vào vào một tập nhánh hữu hạn:
Tập phải được khai báo trước và đóng kín. ReAct nguyên bản, trên giấy, cũng có tập hành động hữu hạn: với HotpotQA chỉ gồm search, lookup và finish.4 Nhưng ở khâu thực thi, mô hình đề xuất hành động bằng văn xuôi. Tập công cụ trên giấy là ba phần tử, còn không gian chuỗi mà mô hình bơi trong đó là toàn bộ .
Ba công cụ, nhưng vô số chuỗi. Runtime vì thế phải gánh hai bài toán: chuỗi vừa sinh có chứa lệnh hay không, và nếu có thì lệnh ấy có nằm trong tập cho phép hay không.
Gọi là parser biến output thành một hành động, hoặc trả về khi thất bại. Một lượt định tuyến hỏng rơi vào đúng một trong ba biến cố:
Ba biến cố này rời nhau, không chồng lên nhau, nên xác suất của chúng cộng được:
Tổng này chỉ đo riêng quyết định định tuyến. Lỗi phân quyền, lỗi mạng hay crash hệ thống thuộc các tầng khác, và không được giấu vào phương trình này.
Function calling kéo xuống mạnh. Structured Outputs đi thêm một bước quyết định: nếu nhánh kế tiếp được khai báo bằng một enum, constrained decoding can thiệp ngay lúc lấy mẫu, loại mọi token không dẫn tới một giá trị hợp lệ. Production không còn phải cầu cho mô hình viết đúng tên hàm; tập lựa chọn đã bị khóa từ lúc sampling. Với decoder bảo chứng, và .
Chữ "xấp xỉ" ở đây không phải khiêm tốn cho có. Ngay trong bài giới thiệu Structured Outputs, OpenAI vẫn nêu hai lối thoát: mô hình có thể trả về một lời từ chối (refusal) thay vì object theo schema, và output có thể bị cắt ngang nếu chạm giới hạn token.3 Harness vẫn phải có nhánh cho hai trường hợp ấy. Chỉ là giờ đây chúng là những trường hợp có tên, không phải một đoạn văn xuôi bất định.
Tuy vậy, vẫn còn nguyên. Schema có thể ép trường next chỉ nhận review hoặc search, nhưng schema không biết email này thực sự cần review hay search. Một lời gọi hàm đúng kiểu vẫn có thể chứa một câu SQL sai, một số tiền sai, một mã khách hàng sai.

Vì vậy harness mang hai nhiệm vụ tách biệt. Thứ nhất, biến mọi output ngoài tập thành một mã lỗi hệ thống, không bao giờ để nó trôi thành một hành động ngầm. Thứ hai, giữ quyền kiểm duyệt một action hợp lệ về cấu trúc trước khi cho nó chạm vào công cụ thật.
Mô hình vẫn được quyền chọn. Thứ bị tước đi không phải năng lực điều hướng, mà là quyền tự bịa ra một lựa chọn có khả năng thực thi.
II. Từ router tối cao xuống worker node
Đóng tập lựa chọn mới chỉ khóa được danh sách đường đi. Câu hỏi gắt hơn vẫn còn: ai giữ chìa khóa biến một đề xuất thành một tác động thật?
Trong một harness có kỷ luật, LLM bị giáng đúng một bậc: từ người cầm lái tiến trình thành một worker node nằm bên trong tiến trình. Nó nhận một phần state, xử lý, rồi nhả ra một đề xuất có cấu trúc. Đề xuất ấy không tự thực thi. Runtime phán xét có nhận nó không, node nào được đánh thức tiếp theo, và hệ thống dừng ở đâu.
Gọi là state toàn cục. Tại mỗi node :
- là phép chiếu, chỉ lọc ra những trường mà node được phép đọc;
- dựng context từ phần dữ liệu đã lọc;
- ép output thành một object có cấu trúc.
Một lượt gọi mô hình khi ấy viết được thành:
Công thức này cũng khép lại một món nợ từ Bài 01. Chain-of-Thought, như đã phân tích ở đó, không phải state mà là context: mô hình viết thêm vào điều kiện của chính nó. Trong harness, chuỗi suy luận ấy được phép dài bao nhiêu cũng được, miễn là nó ở lại bên trong , trong lòng node. Thứ duy nhất bước ra khỏi node là , một object có kiểu. Suy luận là việc riêng của node; tập cạnh của đồ thị thì không phải chỗ để nó viết lên.

Ở tầng ứng dụng, LLM được đối xử như một hàm không giữ trạng thái giữa các lượt: dữ liệu vào, một phân phối output ra. Nhà cung cấp API có thể tối ưu bằng KV cache hay thread, nhưng state mà hệ thống cần để phục hồi và kiểm toán không được bám vào bộ nhớ ngầm ấy. Nó phải nằm trong , được lưu xuống database, dưới quyền của runtime.
Trong bài Building effective agents (tháng 12/2024), Anthropic gọi tên một ranh giới rất gọn.5 Workflow là hệ thống trong đó LLM và công cụ được điều phối qua những đường code định sẵn. Agent là hệ thống trong đó LLM tự điều khiển tiến trình và cách dùng công cụ của mình.
Đó không phải vạch kẻ đen trắng, mà là hai đầu của một dải phổ kiểm soát. Vặn núm về phía workflow, ta có mạng các node nhỏ, rẽ nhánh bằng logic tĩnh, dễ test nhưng kém linh hoạt. Vặn về phía agent, ta có một đồ thị hình sao: LLM ở lõi, công cụ là vệ tinh. Nhưng ngay ở điểm linh hoạt nhất, mô hình vẫn không lướt tự do trên . Nó có thể chọn công cụ nhiều lần, nhưng tập công cụ do code cấp phép. Một dispatcher kiểm tra tên hàm, schema, quyền hạn và số bước tối đa trước khi cho thực thi.

Khác biệt lịch sử vì thế không nằm ở chỗ agent cũ dùng tập mở còn agent mới dùng tập đóng. ReAct năm 2022 cũng có danh sách action hữu hạn. Sự tiến hóa nằm ở cơ chế cưỡng chế. Đời đầu mô tả action bằng prompt, rồi trông vào một cái kéo parser cắt đúng chỗ. Harness trưởng thành nhốt action vào một kênh có kiểu, và bắt runtime đóng dấu trước khi cho gọi hàm.
Độ tự chủ của AI là một núm vặn. Nhưng đặt núm ở đâu thì quyền thực thi cuối cùng vẫn phải thuộc về mã nguồn.
III. Đồ thị hữu hạn: sự ràng buộc của topology
Vòng while vô định của các hệ thống cũ có thể thay bằng một đồ thị trạng thái. Nhưng gọi mọi đồ thị như vậy là DAG (đồ thị có hướng không chu trình) sẽ vô tình xóa đi đặc điểm quan trọng nhất của agent.
Agent sinh ra là để lặp: thử một bước, quan sát kết quả, sửa kế hoạch, rồi thử lại. Bài giới thiệu LangGraph nói thẳng mục tiêu là giúp tạo ra đồ thị có chu trình, thứ agent runtime thường cần.2 Một pipeline thẳng không vòng lặp chỉ là trường hợp đặc biệt.
Harness không cấm chu trình. Harness chỉ buộc chu trình phải có tên, có trạng thái, và có giới hạn.
Một topology cơ bản gồm ba loại thành phần:
- Node: đơn vị tính toán, có thể là gọi LLM, gọi API hay chạy một đoạn Python.
- Edge: tuyến đường được phép đi từ node này sang node khác.
- Conditional edge: tuyến rẽ nhánh do code quyết định, dựa trên state hoặc trên một đề xuất đã qua kiểm duyệt của LLM.
Tại node , mô hình có thể đề xuất đích đến . Runtime áp một luật chuyển nghiêm ngặt:
ở đây không phải một câu mắng mô hình bằng tiếng Anh rồi nhét lại vào context, như vòng xin lỗi ở Bài 02. Nó là một nhánh thật do lập trình viên định nghĩa: ghi log sự cố, kích hoạt fallback, hoặc hủy tiến trình.
Nếu chỉ nhìn luồng điều khiển, hệ thống trông như một máy trạng thái hữu hạn (FSM). Nhưng dữ liệu trong state có thể phình ra không giới hạn, như một danh sách tài liệu hay một chuỗi JSON, nên tên gọi chính xác hơn là máy trạng thái hữu hạn mở rộng (EFSM): luồng điều khiển hữu hạn, dữ liệu mở rộng, và các cạnh được canh bằng điều kiện trên dữ liệu.
Chu trình không phá vỡ tính hữu hạn của tập điều khiển; nó chỉ cho một lần chạy đi qua cùng một node nhiều lần. Agent loop vì thế không còn là một cuộc hội thoại tự nối dài chính nó. Nó thành một quỹ đạo quan sát được: đang ở node nào, đã lặp bao nhiêu lần, state biến đổi ra sao, điều kiện nào cho phép quay lại.
Để chặn vòng lặp vô tận, runtime đặt giới hạn số lần chuyển trạng thái; trong LangGraph là recursion_limit. Nhưng giới hạn số bước không phải giới hạn thời gian. Một node vẫn có thể treo cả tiến trình khi mạng nghẽn. Muốn harness giữ được tiến trình cho trọn, nó cần thêm timeout theo đồng hồ thật, cơ chế hủy, và giới hạn tài nguyên cho từng node.
Quyền lực của dominator
Lợi ích lớn nhất của topology là chuyển các tính chất an toàn từ lời dặn dò sang cấu trúc. Giả sử node Drafting_Email chỉ có hai cạnh ra:
Drafting_Email -> Human_Review
Drafting_Email -> Search_Docs
Dù LLM trả về một khối JSON hoàn hảo đề xuất Send_Email, runtime sẽ ném đi ngay, vì cạnh đó không có trong . Không cần tốn token dặn mô hình "đừng gửi khi chưa duyệt". Cấu trúc đồ thị tự khiến con đường chưa qua duyệt không tồn tại.
Nói bằng ngôn ngữ lý thuyết đồ thị: Human_Review dominates Send_Email. Mọi đường đi từ điểm khởi đầu tới lúc gửi email đều phải xuyên qua điểm duyệt của con người.6
Nhưng cần nói cho đủ: dominator chỉ bảo đảm hệ thống đi qua Human_Review, không bảo đảm con người đã đồng ý. Muốn có điều thứ hai, cạnh Human_Review -> Send_Email phải là một conditional edge canh bằng approval_status == "approved", và trường ấy chỉ được ghi bởi đúng node duyệt, một luật mà mục IV sẽ giao cho reducer. Topology lo đường đi; điều kiện trên cạnh lo nội dung; quyền ghi lo ai được đổi nội dung ấy.

Khi quy tắc an toàn chỉ nằm trong prompt, ta đang tung đồng xu xem mô hình có nghe lời không. Khi nó nằm trong topology, ta có thể kiểm chứng nó trên đồ thị trước cả khi hệ thống chạy.
Dĩ nhiên, chứng minh ấy chỉ đúng ở tầng ứng dụng. Nếu code bên trong một node tự mở socket và gửi email, topology vô dụng. Đó là việc của sandbox, và là chuyện của Bài 04.
IV. State, reducer và hồi kết của cuộn tape vô danh
Sự hỗn loạn của agent đời đầu không chỉ ở chỗ nó đi đâu, mà còn ở chỗ nó "nhớ" bằng cách nào.
Yêu cầu người dùng, luồng suy nghĩ, kết quả công cụ, lỗi runtime, cả những lời xin lỗi, tất cả bị nhét chung vào một mảng messages. Đó chính là cuộn unsigned tape ở Bài 02: dài ra sau mỗi vòng lặp, mọi loại dữ liệu chung một kênh, mọi tác giả viết lên cùng một bề mặt, và luật cập nhật (ai được sửa gì) chủ yếu dựa vào thỏa thuận ngầm.
Nói ReAct "không có state" là oan cho nó. Nó có state, nhưng là state bị làm phẳng thành một dòng lịch sử hội thoại. Nguồn gốc của từng mẩu dữ liệu và quyền ghi đè không được runtime bảo vệ.
Harness khép lại cuộn tape ấy. State trở thành một object có kiểu:
class AgentState(TypedDict):
user_query: str
documents: Annotated[list[DocumentRef], merge_dedup]
draft: str
approval_status: Literal["pending", "approved", "rejected"]
Chạy xong, node không trả về toàn bộ state mà chỉ nhả ra một bản cập nhật . Với mỗi trường , một reducer , thuần code, quyết định cách gộp giá trị mới:
Reducer có thể ghi đè, nối danh sách, lọc trùng, cộng bộ đếm, hoặc thẳng tay ném lỗi nếu bản cập nhật vi phạm một bất biến, chẳng hạn một node không phải Human_Review định ghi vào approval_status.
Nhờ đó, khi gọi mô hình ở node Writer, phép chiếu chỉ lấy user_query và documents. Ba lần timeout ở các node trước không hề xuất hiện trong context của nó.

Đây là chỗ context engineering đạt tới hình dạng trưởng thành: từ nhồi chữ bằng tay sang nhồi chữ bằng kiến trúc.
Hãy đặt công thức này cạnh ở Bài 01. Mô hình vẫn chỉ nhìn thấy ; không có gì thay đổi ở phía nó. Cái thay đổi là không còn do một người gõ prompt hay một cuộn hội thoại tự sinh ra, mà do một hàm có kiểu dựng lên từ state. Context không còn là bãi chứa toàn bộ ký ức của hệ thống. Nó chỉ là một view tạm thời, dựng đúng cho một node, đúng một thời điểm.
State có kiểu cũng không phải viên đạn bạc. Đừng coi TypeError là tường lửa. Một URL độc hại vẫn là một str hợp lệ, và qua được mọi validator kiểm kiểu. Reducer quản cách state thay đổi; validator quản hình dạng dữ liệu; policy quản quyền; sandbox quản giới hạn thực thi. Bốn lớp đi cùng nhau, không lớp nào thay được lớp nào.
Nhưng ít nhất, cuộn băng đã có chữ ký: trường có tên, cập nhật có nguồn gốc, phép gộp có luật, và context chỉ còn là một view phục vụ suy luận.
V. Runtime giữ thời gian, thất bại và bằng chứng
Khi luồng điều khiển đã nằm trong đồ thị và dữ liệu bền đã nằm trong state, runtime gánh phần khó còn lại: thời gian, sự cố, sự can thiệp của con người, và khả năng phục hồi.
Interrupt và checkpoint
Human-in-the-loop không thể chỉ là một lệnh input() treo màn hình console. Server sập là tiến trình mất theo. Trong harness, một điểm chờ cần hai cơ chế: interrupt để dừng ở một ranh giới đã định, và checkpoint để lưu state cùng vị trí trong đồ thị xuống database. Người duyệt bấm nút sau ba ngày, hệ thống chạy tiếp từ checkpoint. LangGraph, chẳng hạn, lưu checkpoint ở mỗi super-step, tức sau mỗi lượt các node chạy xong và trao quyền cho lượt kế.7
Nhưng chạy tiếp lại mở ra một bài toán quen thuộc của hệ phân tán: node vừa sập đã kịp gây ra tác động bên ngoài hay chưa? Nếu node trừ tiền thẻ vừa chạy xong nhưng crash trước checkpoint kế tiếp, lúc chạy lại nó có trừ tiền lần nữa không? Vì vậy các API gây tác động phải có tính lũy đẳng (idempotency): gọi lại nhiều lần với cùng một khóa chỉ có tác động như gọi một lần. Stripe làm điều này bằng idempotency key gắn vào mỗi request.8 Harness không tự giải được idempotency; nó chỉ có thể chuyền khóa xuống, còn bảo đảm phải được thiết kế ở tầng dịch vụ.
Tạm biệt prompt retry
Nếu gọi công cụ thất bại vì mạng nghẽn, dán stack trace vào prompt rồi bảo LLM "hãy thử lại" là hạ sách. Nó biến một lỗi hạ tầng thành bài kiểm tra đọc hiểu cho mô hình, đúng kiểu vòng xin lỗi của Bài 02. Harness tử tế phải có retry budget, exponential backoff, phân loại lỗi thử lại được và không thử lại được, và circuit breaker. Quyết định có thử lại hay không là một nhánh của runtime, không phải của mô hình.
Checkpoint ≠ audit trail
Hai thứ này hay bị gộp làm một, nhưng chúng trả lời hai câu hỏi khác nhau. Checkpoint trả lời "hệ thống chạy tiếp từ đâu?": nó chỉ giữ state ở các ranh giới super-step. Audit trail trả lời "ai đã đề xuất gì, runtime quyết định ra sao, công cụ thực sự đã làm gì?": nó giữ từng sự kiện, kể cả những sự kiện nằm giữa hai checkpoint, như lần gọi tiền bị crash ở trên.

Chiếc đồng hồ phải nằm ở runtime. Mô hình không tự biết một công cụ đã chạy mất năm giây, hay lệnh gọi API đang ở lần thử thứ ba. Những dữ kiện ấy không được để mô hình đoán từ văn xuôi; runtime phải ghi chúng vào audit log.
VI. Tấm gương từ lõi mô hình
Đến đây sẽ có người phản biện: harness chỉ là giàn giáo cho những mô hình còn non. Khi mô hình đủ mạnh, nó sẽ tự biết đường, không cần đồ thị hay luật lệ bên ngoài.
Để trả lời, hãy nhìn vào bên trong chính những mô hình mạnh nhất hiện nay, chẳng hạn kiến trúc Mixture of Experts (MoE) của DeepSeek-V3. Cần nói trước: harness không sinh ra từ MoE, và gating network không phải agent. Đây là một sự tương đồng về cách thiết kế, không phải bằng chứng nhân quả.
Mỗi lớp MoE của DeepSeek-V3 có một expert dùng chung và 256 expert được định tuyến.9 Khi một token đi vào, nó không chạy qua cả 256 expert. Gating network tính điểm tương hợp giữa token và từng expert:
rồi áp luật TopK để chỉ gửi token tới 8 expert, và mỗi token đi tới tối đa 4 node máy. Đáng chú ý là cách DeepSeek-V3 cân bằng tải: một độ lệch được cộng vào điểm, nhưng chỉ dùng để chọn expert nào được gọi; trọng số nhân vào output của expert vẫn lấy từ điểm gốc .9 Điểm số là khuynh hướng của mô hình; quyền quyết định ai được gọi thuộc về một luật chọn nằm đè lên điểm số.

Đặt gating network cạnh harness, có ba nét tương đồng:
- Tập đích đóng. Router của MoE chọn chỉ số trong . Nó có thể định tuyến kém, nhưng không thể "ảo giác" ra expert thứ 257.
- Luật đè lên điểm số. Điểm tương hợp thể hiện khuynh hướng, còn TopK, giới hạn số node và độ lệch tạo thành một luật chọn đứng trên điểm số thuần. Trong harness cũng vậy: LLM chấm điểm hoặc đề xuất, còn code của đồ thị giữ quyền áp policy để chọn đường.
- Không đi qua văn xuôi. Định tuyến diễn ra trên tensor và trả về chỉ số. Không ai in tên expert ra màn hình rồi chờ regex cắt lấy. Biến cố đơn giản không tồn tại ở giao diện này.
Điều đáng học ở đây là một khuôn mẫu chung: thành phần xác suất có thể chấm điểm và đề xuất trong một không gian lựa chọn, nhưng một giao diện hữu hạn và một bộ luật xác định phải giữ quyền biến điểm số thành hành động.
Ở tầng trọng số, các nhà nghiên cứu không bao giờ để mô hình tự viết tên expert rồi hy vọng một parser đọc hiểu. Không có lý do gì ở tầng ứng dụng, nơi hành động có thể gửi email, sửa database hay chạy mã, chúng ta lại chấp nhận một tiêu chuẩn thấp hơn thế.
Cánh cửa xuống tầng đáy
Con đường của kiến trúc AI không phải là chuyện prompt, agent và workflow thay thế nhau. Qua mỗi chặng, ta lấy bớt một phần trách nhiệm khỏi chuỗi token để giao lại cho sự chặt chẽ của kỹ thuật phần mềm.
Prompt engineering tổ chức đầu vào. ReAct cho mô hình tham gia đề xuất bước đi. Function calling tách lệnh khỏi văn xuôi. State có kiểu, graph runtime và Structured Outputs biến những quy ước lỏng lẻo thành cấu trúc có tính cưỡng chế.
Harness kết tinh quá trình lột xác ấy:
- Schema đóng khung đề xuất.
- Dispatcher đóng tập hành động.
- Đồ thị đóng tập đường đi.
- Reducer đóng luật cập nhật state.
- Runtime giữ nhịp thời gian, checkpoint và retry.
- Audit log giữ bằng chứng.
Harness không làm mô hình bớt thông minh. Nó vạch rõ ranh giới nơi trí thông minh được phép biến thành hành động. Mô hình vẫn có thể là bộ não sáng giá nhất của hệ thống, nhưng không còn là kẻ nắm quyền tối cao.
Với harness, và bị runtime chặn lại. Nhưng vẫn còn nguyên.
Một node hợp lệ vẫn có thể chạy một câu SQL xóa nhầm dữ liệu. Một đoạn Python do LLM viết vẫn có thể lặng lẽ mở kết nối mạng, đọc /etc/shadow hay tải mã độc về. Topology có thể tự hào tuyên bố "tôi không có cạnh nào tới Send_Email", nhưng hoàn toàn bất lực nếu code chạy bên trong node tự mở socket và gửi email vượt rào.
Đến đây, kiến trúc không thể chỉ phòng thủ ở tầng ứng dụng. Nó phải xuống tới tầng hệ điều hành.
Bài 04 sẽ đi xuống tầng ấy: sandbox, microVM và nguyên lý zero-trust, nơi đoạn code do AI viết chạy trong một máy ảo riêng như Firecracker, với nhân hệ điều hành riêng, mạng và hệ thống file bị cắt tới mức tối thiểu, để dù có muốn, nó cũng không chạm được vào máy chủ thật.
Mô hình đề xuất đường đi. Harness quyết định đường nào được phép tồn tại. Runtime quyết định bước nào được chạy. Và sandbox quyết định bước ấy được chạm tay tới đâu.
- OpenAI, Function calling and other API updates, 13/6/2023. ↩
- LangChain, LangGraph, 17/1/2024: LangGraph ra đời để "better enable creation of cyclical graphs, often needed for agent runtimes". ↩
- OpenAI, Introducing Structured Outputs in the API, 6/8/2024. Bài viết nêu riêng trường hợp mô hình từ chối (trường
refusal) và output bị cắt khi chạm giới hạn token. ↩ - Shunyu Yao và cộng sự, ReAct: Synergizing Reasoning and Acting in Language Models, ICLR 2023. Không gian hành động cho HotpotQA ở mục 3.1. ↩
- Anthropic, Building effective agents, 19/12/2024: "Workflows are systems where LLMs and tools are orchestrated through predefined code paths. Agents, on the other hand, are systems where LLMs dynamically direct their own processes and tool usage". ↩
- Về khái niệm dominator và cách tính cây dominator, xem Keith D. Cooper, Timothy J. Harvey, Ken Kennedy, A Simple, Fast Dominance Algorithm, Rice University, 2001. ↩
- LangChain, LangGraph: Checkpointers. ↩
- Stripe, Idempotent requests. ↩
- DeepSeek-AI, DeepSeek-V3 Technical Report, 2024, mục 2.1.2: "the bias term is only used for routing. The gating value, which will be multiplied with the FFN output, is still derived from the original affinity score". ↩
— espresso.hoagg