YouMind
Đăng nhập

Xây dựng Harness tùy chỉnh với Pi và Jev

@omarsar0
TIẾNG ANH23 thg 9, 2026
102K
1.1K
115
41
2.2K

TL;DR

Hướng dẫn này giúp lập trình viên xây dựng harness AI agent tùy chỉnh sử dụng Pi SDK và Jev, một mô hình chuyên biệt cho việc ra quyết định. Bài viết minh họa cách triển khai các cổng an toàn, định tuyến mô hình động và xác minh câu trả lời nhằm tối ưu hóa hiệu suất và chi phí.

AI agent là một mô hình ngôn ngữ hoạt động theo vòng lặp. Nó đọc tác vụ, sử dụng một công cụ như "đọc tệp này" hoặc "xóa tệp kia", xem xét kết quả và tiếp tục cho đến khi hoàn thành công việc. Mỗi lần mô hình yêu cầu sử dụng một công cụ, yêu cầu đó được gọi là một tool call (lệnh gọi công cụ).

Đoạn mã chạy vòng lặp này được gọi là harness. Mô hình quyết định nó muốn làm gì. Harness thực thi quyết định đó và cũng quy định những gì mô hình được phép làm.

Một harness tốt sẽ đưa ra rất nhiều quyết định nhỏ trong suốt quá trình. Mô hình nào nên xử lý yêu cầu này? Lệnh gọi công cụ này có an toàn để chạy không? Câu trả lời này đã đủ tốt để gửi lại chưa? Hầu hết các harness giải quyết những câu hỏi này bằng cách hỏi một mô hình chat và đọc phản hồi của nó. Việc đó tốn chi phí cho một lần gọi mô hình hoàn chỉnh mỗi lần, nên trên thực tế, phần lớn các bước kiểm tra thường bị bỏ qua.

Jev từ TypeSafe AI là một mô hình nhỏ được xây dựng chỉ dành riêng cho những quyết định này. Bạn mô tả tình huống và đặt vài câu hỏi, nó sẽ trả lời từng câu bằng một con số. Nó không bao giờ tạo ra văn bản.

Điều này đặc biệt quan trọng khi bạn xây dựng một custom harness (harness tùy chỉnh) — tức là tự tạo vòng lặp agent của riêng mình thay vì dùng một agent có sẵn. Custom harness cho phép bạn chọn mô hình nào sẽ chạy, agent được phép can thiệp vào đâu và thế nào mới được coi là hoàn thành. Jev giúp các bước kiểm tra đằng sau những lựa chọn đó rẻ đến mức bạn có thể chạy ở mọi bước.

Trong hướng dẫn này, bạn sẽ xây dựng một harness bằng Pi SDK, một bộ công cụ TypeScript để phát triển agent, và sử dụng Jev ở ba vị trí. Cuối cùng, bạn sẽ chạy harness vừa hoàn thiện trong một sandbox trực tiếp và tự tay thay đổi các thiết lập của nó.

Truy cập hướng dẫn tương tác đầy đủ và playground tại đây:

https://academy.dair.ai/resources/jev-decisions-in-a-pi-sdk-harness

Hướng dẫn này lấy cảm hứng từ bài viết Building a Harness with Jev của Sydney Runkle trên blog LangChain, nơi giới thiệu model routing (định tuyến mô hình) và tool gating (kiểm soát công cụ) dưới dạng middleware có sẵn của LangChain. Ở đây, bạn sẽ tự tay xây dựng lại những ý tưởng đó trên Pi SDK, sau đó bổ sung thêm hai mẫu nữa để xử lý lỗi và kiểm tra câu trả lời.

Bạn sẽ xây dựng những gì

Harness gồm ba phần. Mỗi phần sẽ hỏi Jev một câu hỏi vào một thời điểm khác nhau, và phần còn lại của hướng dẫn này sẽ gọi chúng bằng những tên sau.

elvis - inline image

Ví dụ xuyên suốt bài viết là một agent làm việc trong một thư mục chứa ghi chú khảo sát đường mòn, mỗi chuyến đi là một tệp. Nó có thể đọc, ghi và thực sự xóa được những ghi chú đó — đây chính là lý do lớp gate (cổng kiểm soát) lại quan trọng.

Jev là gì

TypeSafe gọi Jev là một System One model (mô hình Hệ thống 1). Cái tên này bắt nguồn từ nhà tâm lý học Daniel Kahneman, người đã mô tả hai chế độ tư duy. System One nhanh và tự động, giống như việc bạn biết ngay chiếc chảo đang nóng. System Two chậm rãi và có chủ đích, giống như khi bạn làm phép chia dài.

Trong harness này, một mô hình ngôn ngữ thông thường đảm nhận phần việc chậm rãi là đọc tệp và viết câu trả lời. Jev đưa ra những phán đoán nhanh xung quanh quá trình đó. Jev đủ rẻ và đủ nhanh để bạn có thể hỏi về mọi lệnh gọi công cụ, chứ không chỉ những lệnh mà bạn dự đoán là rủi ro.

elvis - inline image

Mỗi con số là một xác suất từ 0 đến 1. Mức 0.83 nghĩa là Jev khá chắc chắn câu trả lời là "có". Mức 0.03 nghĩa là nó khá chắc chắn câu trả lời là "không".

Ba loại câu hỏi

Mọi yêu cầu gửi tới Jev đều có hai phần. State (trạng thái) là tình huống bạn muốn đánh giá, chẳng hạn một lệnh gọi công cụ hay một yêu cầu của người dùng. Questions (câu hỏi) là những gì bạn muốn biết về tình huống đó. Jev trả lời tất cả câu hỏi trong cùng một lần gọi, đồng thời, nên việc hỏi ba câu cũng mất thời gian gần bằng hỏi một câu.

Jev hỗ trợ ba loại câu hỏi. Loại đầu tiên, Jev gọi là noul, là câu hỏi dạng có/không.

elvis - inline image

Jev chỉ biết những gì bạn nói cho nó, vì vậy hãy mô tả mọi tùy chọn và mọi mức độ bằng ngôn ngữ rõ ràng. Chính những đoạn mô tả này là prompt.

Thiết lập

Jev được cung cấp qua OpenRouter, một dịch vụ cho phép bạn truy cập nhiều mô hình AI chỉ với một API key. Một khóa duy nhất này dùng được cho cả mô hình ngôn ngữ lẫn Jev. Hãy cài đặt hai gói Pi và thiết lập khóa của bạn.

bash
1npm install @earendil-works/pi-agent-core @earendil-works/pi-ai
bash
1OPENROUTER_API_KEY=...

Jev có địa chỉ web riêng, tách biệt với endpoint chat thông thường. Luôn chỉ định chính xác phiên bản, ví dụ typesafe/jev-1.13. Toàn bộ Jev client chỉ gói gọn trong một lệnh gọi fetch.

javascript
1const JEV = "typesafe/jev-1.13";
2
3async function ask(state: unknown, questions: unknown) {
4 const response = await fetch("https://openrouter.ai/api/alpha/decisions", {
5 method: "POST",
6 headers: {
7 "Content-Type": "application/json",
8 Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
9 },
10 body: JSON.stringify({ model: JEV, state, questions }),
11 signal: AbortSignal.timeout(2_000),
12 });
13 if (!response.ok) throw new Error(await response.text());
14 return response.json();
15}

Agent phải chờ Jev trả lời, nên lệnh gọi sẽ tự hủy sau hai giây. Một phản hồi bình thường chỉ mất từ 200 đến 400 mili giây.

Vị trí tích hợp Jev

Lớp Agent của Pi sẽ chạy vòng lặp giúp bạn. Nó cho phép mã của bạn chen vào những thời điểm định trước trong vòng lặp đó. Những điểm này được gọi là hook. Harness sử dụng một hook cho mỗi phần trong ba phần của nó.

elvis - inline image

Lớp gate đầu tiên

Hãy bắt đầu với phiên bản gate đơn giản nhưng hữu ích nhất. Trước mỗi lệnh gọi công cụ, hãy hỏi Jev một câu hỏi có/không và chặn lệnh gọi nếu câu trả lời nghiêng về "có".

javascript
1import { Agent } from "@earendil-works/pi-agent-core";
2
3const agent = new Agent({
4 initialState: { systemPrompt, model, tools },
5 streamFn: models.streamSimple.bind(models),
6
7 beforeToolCall: async ({ toolCall, args }) => {
8 const { answers } = await ask(
9 { tool: toolCall.name, arguments: args },
10 {
11 destructive: {
12 type: "noul",
13 instructions: "This tool call destroys or overwrites data that was not created by this run.",
14 criteria: {
15 true: "Deletes files, truncates or overwrites existing content, drops data, or force-pushes over history.",
16 false: "Reads, lists, creates a new file, or appends to a file this run already created.",
17 },
18 },
19 },
20 );
21
22 if (answers.destructive.noul >= 0.65) {
23 return { block: true, reason: "Blocked: this looks destructive.", terminate: true };
24 }
25 },
26});

Con số 0.65 là một threshold (ngưỡng) — ranh giới mà tại đó harness ngừng tin tưởng một lệnh gọi. Bất cứ thứ gì Jev chấm điểm bằng hoặc cao hơn ngưỡng này đều bị chặn.

Giờ thì một agent cố gắng xóa ghi chú của bạn sẽ bị dừng lại trước khi hành động xóa diễn ra. Xóa một ghi chú đạt khoảng 0.83, còn đọc một ghi chú chỉ đạt 0.01. Tuy nhiên, lớp gate này còn khá thô. Việc tạo một tệp hoàn toàn mới cũng đạt khoảng 0.70, nên thao tác đó cũng bị chặn luôn.

Chi phí cho mỗi lần hỏi là rất nhỏ. Jev tính phí 0.042 USD cho mỗi triệu token đầu vào, chưa bằng một phần ba giá đầu vào của GLM 5.3 Flash — mô hình rẻ hơn trong hai mô hình mà harness này sử dụng.

Từ lớp gate đầu tiên đến harness hoàn chỉnh

Lớp gate đầu tiên hoạt động được, nhưng vẫn để ngỏ bốn lỗ hổng.

  1. Ngưỡng được chôn sâu bên trong hook, nên rất khó điều chỉnh hoặc kiểm thử.
  2. Mọi yêu cầu đều chạy trên cùng một mô hình, bất kể dễ hay khó.
  3. Không có quy định nào cho trường hợp không thể kết nối tới Jev.
  4. Không có gì kiểm tra xem câu trả lời cuối cùng có tốt hay không.

Mỗi phần được đánh số dưới đây sẽ lấp đầy một lỗ hổng.

1. Gom ngưỡng về một chỗ

Phần này cải thiện lớp gate.

Ngưỡng quyết định những gì agent được phép làm, và bạn sẽ phải điều chỉnh chúng liên tục khi thấy kết quả thực tế. Vì vậy, hãy đưa chúng ra khỏi hook và gom vào một hàm đơn giản: decideGate(). Hàm này nhận các con số từ Jev và trả về một verdict (phán quyết): quyết định cuối cùng của harness đối với một lệnh gọi. Việc giữ các quy tắc này ở một nơi duy nhất được gọi là policy (chính sách).

Policy cũng bổ sung thêm một lựa chọn trung gian. Một ngưỡng chỉ có thể cho phép hoặc chặn. Hai ngưỡng sẽ tạo ra ba phán quyết.

  • Bằng hoặc cao hơn blockAt: lệnh gọi bị chặn.
  • Thấp hơn askAt: lệnh gọi được thực thi.
  • Nằm giữa hai ngưỡng: lệnh gọi phải chờ người thật phê duyệt.

Khoảng giữa này bắt được những lệnh gọi mà Jev không chắc chắn — những trường hợp mà một ngưỡng duy nhất sẽ xử lý sai theo hướng này hay hướng khác.

elvis - inline image
javascript
1export function decideGate(signals: GateSignals, policy = DEFAULT_GATE_POLICY): GateVerdict {
2 const worst = Math.max(
3 signals.destructive.noul,
4 signals.irreversible.noul,
5 signals.outsideWorkspace.noul,
6 );
7 if (worst >= policy.blockAt) return { action: "block", ... };
8 if (worst >= policy.askAt) return { action: "ask", ... };
9 return { action: "allow", ... };
10}

Vì đây chỉ là một hàm thông thường, bạn có thể kiểm thử nó bằng cách truyền vào những con số như trên mà không cần gọi tới mô hình thực tế.

2. Chọn mô hình cho từng yêu cầu

Phần này bổ sung router (bộ định tuyến).

Có những yêu cầu rất dễ, như đọc một tệp. Có những yêu cầu lại khó, như truy tìm nguyên nhân một lỗi hỏng hóc. Chạy mọi thứ trên mô hình mạnh nhất thì tốn tiền, còn chạy mọi thứ trên mô hình rẻ thì lại cho ra câu trả lời yếu ớt ở những yêu cầu khó. Router sẽ ghép mỗi yêu cầu với đúng tier (hạng) phù hợp — tức là mô hình nhanh, rẻ hoặc mô hình mạnh, đắt đỏ.

Trước khi một yêu cầu bắt đầu, router hỏi Jev hai câu hỏi trong cùng một lần gọi. Một choice (lựa chọn) để chọn tier, và một score (điểm) để đánh giá độ phức tạp của yêu cầu.

elvis - inline image
javascript
1const ROUTER_QUESTIONS = {
2 tier: choice("Which model tier should handle this request?", {
3 fast: "Reading one file, pulling a fact out of it, or a small edit in a single place.",
4 powerful: "Work that spans several files, or a failure with no obvious cause.",
5 }),
6 complexity: score("How much reasoning does this request need?", [
7 "Mechanical. One step, no judgement.",
8 "Localized. A few steps inside one area.",
9 "Architectural. Many moving parts or an unknown root cause.",
10 ]),
11};

Policy của router sử dụng hai câu trả lời này như sau.

  • Nếu điểm complexity cao, dùng mô hình mạnh, kể cả khi Jev chọn "fast".
  • Nếu Jev không tự tin với lựa chọn của mình, dùng mô hình mạnh cho an toàn.
  • Nếu không, dùng mô hình mà Jev đã chọn.
javascript
1if (complexity.score >= policy.escalateAtComplexity) return powerful;
2if (tierAnswer.confidence < policy.minConfidence) return policy.fallbackTier;
3return tierAnswer.choice;

Đây chỉ là một ví dụ về policy. Policy của bạn có thể cân nhắc các câu trả lời theo cách khác để phù hợp với lĩnh vực của mình, chẳng hạn ưu tiên mô hình rẻ cho một tác vụ batch khối lượng lớn, hoặc luôn đẩy lên mô hình mạnh bất cứ thứ gì chạm tới môi trường production. Jev chỉ cung cấp câu trả lời; mã của bạn mới là thứ quyết định làm gì với chúng.

Tại sao chỉ chọn một lần

Router chỉ chọn mô hình một lần khi yêu cầu bắt đầu và giữ nguyên cho đến khi yêu cầu hoàn tất. Đó là vì prompt caching (bộ nhớ đệm prompt).

Mỗi khi agent thực hiện một bước, mô hình phải đọc lại toàn bộ cuộc hội thoại từ đầu đến lúc đó. Các nhà cung cấp AI lưu lại những cuộc hội thoại vừa được đọc để việc đọc lại trở nên rẻ hơn. Nhưng mỗi mô hình lại có kho lưu trữ riêng. Nếu bạn đổi mô hình giữa chừng, mô hình mới sẽ phải đọc lại mọi thứ từ đầu với mức giá đầy đủ.

Nhà sáng lập Jev đã phân tích các con số này trong một tài liệu thiết kế về coding agent. Trong một phiên làm việc dài, việc chuyển từ Claude Opus sang Sonnet rẻ hơn rồi quay lại tốn thêm khoảng 50% so với việc chỉ dùng Opus từ đầu đến cuối. Vậy nên hãy chọn mô hình ngay từ đầu, khi cuộc hội thoại còn ngắn, và gắn bó với nó.

3. Lên phương án khi Jev gặp sự cố

Phần này thay đổi cả gate lẫn router.

Một khi harness hỏi Jev về mọi lệnh gọi công cụ, agent sẽ phụ thuộc vào Jev. Giống như bất kỳ dịch vụ trực tuyến nào, Jev có thể bị chậm hoặc sập. Hãy quyết định trước mỗi phần sẽ làm gì khi không nhận được câu trả lời. Lựa chọn đúng sẽ khác nhau ở từng phần.

Gate chặn lệnh gọi. Nếu gate không thể hỏi Jev, nó không có cách nào biết lệnh gọi đó có an toàn hay không. Để lọt qua có thể dẫn đến việc xóa tệp, nên gate sẽ từ chối. Giới kỹ sư gọi đây là failing closed (thất bại ở trạng thái đóng), giống như một cánh cửa tự động khóa khi mất điện.

Router dùng mô hình mạnh. Nếu router không thể hỏi Jev, nó không biết yêu cầu đó khó đến đâu. Mô hình mạnh có thể xử lý mọi thứ, nên yêu cầu vẫn nhận được câu trả lời tốt, chỉ là bạn phải trả thêm một chút chi phí. Đây là failing open (thất bại ở trạng thái mở), cho phép công việc tiếp tục.

elvis - inline image

4. Kiểm chứng câu trả lời

Phần này bổ sung verifier (bộ kiểm chứng). Trong các harness agent, verifier là bước kiểm tra sản phẩm của agent trước khi coi nó là hoàn thành.

Một agent có thể kết thúc với một câu trả lời thiếu sót, hoặc khẳng định những điều mà nó chưa từng thực sự kiểm tra trong tệp. Người đọc thường không nhận ra điều đó. Việc kiểm tra câu trả lời trước khi trả về sẽ phát hiện ra lỗi này trong lúc agent vẫn còn cơ hội thử lại.

Verifier gửi cho Jev câu trả lời đã hoàn thiện cùng với các tệp và kết quả công cụ làm cơ sở cho nó. Jev chấm điểm chất lượng câu trả lời và cho biết các khẳng định trong đó có grounded (có căn cứ) hay không — tức là có được hỗ trợ bởi những gì agent thực sự đọc hay không.

elvis - inline image
javascript
1const VERIFY_QUESTIONS = {
2 quality: score("How well does the answer satisfy the request?", [
3 "Does not answer the request.",
4 "Partly answers it, with a gap the reader would notice.",
5 "Fully answers the request.",
6 ]),
7 grounded: noul("Every factual claim is supported by the files or tool results in the transcript."),
8};

Hai quy tắc ngăn agent thử lại mãi mãi. Nó chỉ được tối đa hai lần thử tổng cộng. Và khi Jev không tự tin vào điểm số của chính mình, harness sẽ chấp nhận câu trả lời thay vì tốn tiền cho một lần thử khác.

An toàn và ghi log

Jev trả về cho bạn một xác suất, mà xác suất thì có thể sai. Vì vậy, bất cứ điều gì mã thuần túy có thể kiểm tra chắc chắn thì nên được kiểm tra bằng mã. Trong harness này, mọi công cụ xử lý tệp đều từ chối bất kỳ đường dẫn nào nằm ngoài thư mục dự án, bất kể Jev nói gì. Hãy dành Jev cho những phán đoán mà mã không thể tự đưa ra.

Gate chỉ nhìn vào bản thân lệnh gọi công cụ, tức là tên công cụ và các tham số đầu vào của nó. Điều này giúp ích cho việc chống prompt injection, khi văn bản ẩn trong một tệp hoặc trang web lừa mô hình thực hiện hành vi gây hại. Gate không bao giờ thấy mánh khóe đó, nhưng nó vẫn thấy được lệnh gọi độc hại sinh ra từ mánh khóe ấy.

Hãy ghi log mọi quyết định kèm theo các con số đằng sau. Log giải thích tại sao một thứ gì đó bị chặn và cung cấp dữ liệu thực tế để thiết lập ngưỡng. Harness ghi mỗi quyết định thành một dòng vào tệp decisions.jsonl. Gate nhìn thấy mọi thứ agent cố gắng làm, nên log sẽ che đi địa chỉ email và khóa bí mật, đồng thời rút ngắn các đầu vào quá dài.

Thử ngay

Sandbox bên dưới chạy harness hoàn chỉnh từ hướng dẫn này, kết nối trực tiếp với Jev thật. Nó hoạt động trên thư mục ghi chú khảo sát đường mòn, và công cụ delete_path của nó thực sự có thể xóa chúng.

Thử tại đây: https://academy.dair.ai/resources/jev-decisions-in-a-pi-sdk-harness

Tại sao nên tự xây dựng harness

Các agent có sẵn đưa ra những quyết định này bằng luật lệ tích hợp sẵn của chúng. Một custom harness đặt chúng vào chính mã của bạn. Bạn chọn mô hình, thiết lập ngưỡng, quyết định khi nào cần người thật can thiệp và đọc được trong log chính xác lý do từng lệnh gọi được thực hiện.

Jev là thứ biến điều đó thành khả thi. Mỗi quyết định chỉ mất vài trăm mili giây và tốn một phần rất nhỏ của một xu, nên bạn có thể thêm bước kiểm tra ở bất cứ đâu công việc cần, chứ không chỉ ở những nơi bạn đủ ngân sách cho một lần gọi mô hình hoàn chỉnh. Ba phần trong hướng dẫn này chỉ là điểm khởi đầu. Một custom harness cho lĩnh vực của riêng bạn có thể hỏi Jev bất kỳ câu hỏi nào quan trọng ở đó.

Các ứng dụng khác

Ba phần tương tự cũng hoạt động tốt ngoài phạm vi coding agent.

  • Bot review code. Chấm điểm từng thay đổi được đề xuất và chỉ hiển thị cho người thật những thay đổi đáng đọc.
  • Dọn dẹp dữ liệu. Hỏi xem hai bản ghi có mô tả cùng một thứ không trước khi gộp chúng, và để lại những cặp không chắc chắn cho người xử lý.
  • Pipeline xử lý tài liệu. Chấm điểm từng trang được trích xuất và chỉ chạy lại những trang điểm thấp.
  • Hàng đợi phê duyệt. Chỉ những lệnh gọi nằm trong khoảng "hỏi người" mới đến tay người duyệt.

Trong mọi trường hợp, mô hình ngôn ngữ đảm nhận phần việc mở, còn Jev trả lời những câu hỏi nhỏ xoay quanh nó.

Các câu hỏi, ngưỡng và policy trong hướng dẫn này là ví dụ để học tập, không phải thiết lập production đã được tinh chỉnh. Chúng tôi đang đo lường hiệu năng xem những thay đổi này ảnh hưởng thế nào đến chi phí và chất lượng câu trả lời, và một hướng dẫn tiếp theo với các kết quả đó sẽ sớm ra mắt.

Tôi đã dành một buổi tối cùng Opus 5.5 để hoàn thiện hướng dẫn và sandbox này. Nếu bạn gặp bất kỳ vấn đề nào, hãy nhắn tin trực tiếp cho tôi. Cứ thoải mái sao chép bài viết này và đưa cho agent của bạn để tiếp tục thử nghiệm các ý tưởng.

Lưu một chạm

Đọc sâu bài viết viral bằng AI trong YouMind

Lưu nguồn, đặt câu hỏi tập trung, tóm tắt lập luận và biến một bài viết viral thành các ghi chú có thể tái sử dụng trong một không gian làm việc AI duy nhất.

Khám phá YouMind
Dành cho nhà sáng tạo

Biến Markdown của bạn thành bài viết 𝕏 gọn gàng

Khi bạn đăng bài viết dài của riêng mình, việc định dạng hình ảnh, bảng và khối mã cho 𝕏 rất mệt mỏi. YouMind biến cả bản nháp Markdown thành một bài viết 𝕏 gọn gàng, sẵn sàng để đăng.

Thử Markdown sang 𝕏

Thêm pattern để giải mã

Bài viết viral gần đây

Khám phá thêm bài viết viral