YouMind
登入

使用 Pi 和 Jev 建立自訂 Harness

@omarsar0
英語2026年9月23日
102K
1.1K
115
41
2.2K

TL;DR

本教學指南將引導開發人員使用 Pi SDK 和專門用於決策的模型 Jev 來建立自訂 AI Agent harness。內容示範如何實作安全閘門、動態模型路由以及答案驗證,以最佳化效能與成本。

AI Agent 是一種會不斷循環運作的語言模型。它讀取任務、使用像是「讀取這個檔案」或「刪除那個檔案」的工具、檢視結果,然後持續執行直到工作完成。每次模型要求使用工具時,這個請求就稱為 tool call(工具呼叫)。

負責執行這個循環的程式碼稱為 harness。由模型決定要做什麼,而 harness 負責執行,同時也決定模型被允許做什麼。

一個好的 harness 會在過程中做出許多小決定:該由哪個模型處理這個請求?這個 tool call 執行起來安全嗎?這個答案夠好到可以直接回傳嗎?大多數的 harness 都是透過詢問聊天模型並讀取其回覆來回答這些問題。但這樣每次都會消耗一次完整的模型呼叫成本,因此在實務上,大部分檢查往往會被跳過。

來自 TypeSafe AI 的 Jev 是一個專為這些決策打造的小型模型。你描述情境、問幾個問題,它就會用數字逐一回答,而且完全不會產生文字。

當你要建立 自訂 harness(也就是自己寫 Agent 循環,而不是用現成的 Agent)時,這一點最為關鍵。自訂 harness 讓你能選擇要跑哪些模型、Agent 可以碰觸哪些東西,以及怎樣才算完成。Jev 讓這些決策背後的檢查成本低到可以在每一步都執行。

在這份教學中,你會使用 Pi SDK(一套用來建構 Agent 的 TypeScript 工具包)來建立 harness,並在三個地方使用 Jev。最後,你會在即時沙盒中執行完成的 harness,並自己動手調整設定。

在這裡存取完整的互動式教學與遊樂場:

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

本指南的靈感來自 Sydney Runkle 在 LangChain 部落格發表的 Building a Harness with Jev,該文章將模型路由與工具把關展示為現成的 LangChain 中介軟體。而在這裡,你會用 Pi SDK 自己實作相同的概念,並額外加入兩個模式來處理失敗與檢查答案。

你會建立什麼

這個 harness 分為三個部分。每個部分都會在不同時機點向 Jev 提問,接下來的內容會用這些名稱來稱呼它們。

elvis - inline image

文中的範例是一個在步道勘查筆記資料夾中工作的 Agent,每次出遊紀錄成一個檔案。它可以讀取、寫入,甚至真的刪除這些筆記——這就是為什麼把關機制如此重要。

Jev 是什麼

TypeSafe 將 Jev 稱為 System One model(系統一模型)。這個名稱源自心理學家 Daniel Kahneman,他描述了兩種思考模式:系統一快速且自動,就像直覺知道平底鍋很燙;系統二緩慢且深思熟慮,就像在做長除法。

在這個 harness 中,一般的語言模型負責讀取檔案和撰寫答案等慢工細活,而 Jev 則負責周邊的快速判斷。Jev 夠便宜也夠快,讓你可以在每一次 tool call 時都詢問它,而不只是針對那些你預期有風險的操作。

elvis - inline image

每個數字都是介於 0 到 1 之間的機率。0.83 代表 Jev 相當確定答案是「是」;0.03 則代表它相當確定答案是「否」。

三種問題類型

每個 Jev 請求都包含兩個部分。state(狀態)是你希望被判斷的情境,例如某個 tool call 或使用者的請求;questions(問題)則是你想了解的事項。Jev 會在一次呼叫中同時回答所有問題,因此問三個問題跟問一個問題花的時間差不多。

Jev 支援三種問題類型。第一種被 Jev 稱為 noul,也就是是非題。

elvis - inline image

Jev 只知道你告訴它的資訊,所以請用白話文清楚描述每個選項與每個層級。這些描述就是提示詞。

環境設定

你可以透過 OpenRouter 使用 Jev,這項服務讓你用一組 API 金鑰就能存取多種 AI 模型。這一組金鑰可同時用於語言模型與 Jev。安裝兩個 Pi 套件並設定你的金鑰。

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

Jev 有自己的網址,與一般聊天用的不同。請務必指定確切版本,例如 typesafe/jev-1.13。整個 Jev 客戶端就是一次 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 會等待 Jev 回應,因此呼叫會在兩秒後逾時放棄。一般回應大約需要 200 到 400 毫秒。

Jev 的接入點

Pi 的 Agent 類別會幫你執行循環,並讓你的程式碼能在循環中的特定時刻運行。這些位置稱為 hooks(鉤子)。這個 harness 的三個部分各使用了一個 hook。

elvis - inline image

第一道防線

先從最簡單實用的把關機制開始。在每次 tool call 之前,問 Jev 一個是非題,如果答案看起來像「是」,就阻擋該次呼叫。

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});

這裡的 0.65 是 threshold(閾值),也就是 harness 不再信任該次呼叫的分界點。只要 Jev 給出的分數達到或超過這個值,呼叫就會被阻擋。

現在,如果 Agent 試圖刪除你的筆記,動作會在真正刪除前被攔下。刪除筆記的分數約為 0.83,而讀取筆記只有 0.01。不過這道防線有點粗糙:建立全新檔案的分數約為 0.70,所以也會被阻擋。

提問的成本非常低。Jev 每百萬輸入 token 收費 0.042 美元,不到本 harness 使用的兩個模型中較便宜的 GLM 5.3 Flash 輸入價格的三分之一。

從第一道防線到完整 harness

第一道防線確實有用,但留下了四個缺口。

  1. 閾值埋在 hook 裡面,難以調整或測試。
  2. 無論難易,每個請求都跑在同一個模型上。
  3. 沒有定義連不上 Jev 時該怎麼處理。
  4. 沒有檢查最終答案的品質。

以下每個編號章節會補上一個缺口。

1. 集中管理閾值

本章節會改良把關機制。

閾值決定了 Agent 能做什麼,而當你看到實際結果後,會需要經常調整它們。因此,把它們從 hook 移到一個單純的函式 decideGate() 中。它接收 Jev 的數字,並回傳 verdict(裁決):也就是 harness 對該次呼叫的最終決定。將這些規則集中在一處的做法稱為 policy(策略)。

這個 policy 還加入了中間選項。單一閾值只能決定放行或阻擋,而兩個閾值能產生三種裁決。

  • 達到或超過 blockAt,阻擋呼叫。
  • 低於 askAt,執行呼叫。
  • 介於兩者之間,等待人工核准。

這個中間區間能攔截 Jev 不確定的呼叫,若只用單一分界點,無論怎麼設都一定會誤判其中一邊。

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}

因為它只是一個普通函式,你可以直接傳入像上面那樣的數字來測試,完全不需要連接實際模型。

2. 依請求挑選模型

本章節會加入路由器。

有些請求很簡單,例如讀取一個檔案;有些則很困難,例如追查某件事壞掉的原因。全部用最強的模型跑會浪費錢,但全部用便宜的模型跑,又會讓困難請求得到薄弱的答案。路由器會將每個請求配對到合適的 tier(層級),也就是快速便宜的模型,或是強大昂貴的模型。

在請求開始前,路由器會在一次呼叫中問 Jev 兩個問題:一個 choice(選擇)用來挑層級,一個 score(分數)用來評估請求的複雜度。

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 會這樣運用這兩個答案。

  • 如果複雜度分數很高,就用強大的模型,即使 Jev 選了快速模型。
  • 如果 Jev 對自己的選擇沒信心,為了保險起見就用強大的模型。
  • 否則,就用 Jev 挑選的模型。
javascript
1if (complexity.score >= policy.escalateAtComplexity) return powerful;
2if (tierAnswer.confidence < policy.minConfidence) return policy.fallbackTier;
3return tierAnswer.choice;

這只是一種 policy 範例。你可以根據自身領域,用不同方式權衡這些答案,例如在大量批次作業中偏向便宜方案,或是一律升級任何涉及正式環境的操作。Jev 只負責提供答案,怎麼運用則由你的程式碼決定。

為什麼只挑一次

路由器只在請求開始時挑選一次模型,並沿用到請求結束。這是因為 prompt caching(提示快取)。

Agent 每執行一步,模型都要重新讀取到目前為止的整段對話。AI 供應商會儲存最近讀取過的對話,讓重新讀取的成本變低。但每個模型都有自己的儲存空間,如果中途切換模型,新模型就得用原價重新讀取所有內容。

Jev 的創辦人在一份關於程式設計 Agent 的設計文件中算過這筆帳:在一次長時間的對話中,從 Claude Opus 切換到較便宜的 Sonnet 再切回來,成本比全程使用 Opus 高出約 50%。所以要在對話還很短的時候就先選好模型,然後一路用到底。

3. 預想 Jev 掛掉的情況

本章節會同時修改把關機制與路由器。

一旦 harness 對每個 tool call 都詢問 Jev,Agent 就會依賴 Jev。而就像任何線上服務一樣,Jev 也可能變慢或掛掉。請事先決定每個部分在得不到回應時該怎麼做,而各部分的正確做法並不相同。

把關機制會阻擋呼叫。 如果把關機制無法詢問 Jev,它就無從得知這次呼叫是否安全。放行可能會導致檔案被刪,因此它會拒絕。工程師稱這種做法為 failing closed(失效關閉),就像停電時會自動上鎖的門。

路由器會使用強大的模型。 如果路由器無法詢問 Jev,它就不知道請求有多困難。強大的模型什麼都能處理,所以請求依然能得到好答案,只是你要多付一點錢。這就是 failing open(失效開放),讓工作繼續進行。

elvis - inline image

4. 驗證答案

本章節會加入驗證器。在 Agent harness 中,verifier(驗證器)是在工作被視為完成前,檢查 Agent 成果的步驟。

Agent 可能會給出一個漏東漏西的答案,或者聲稱了某些它其實從未在檔案中查證過的事情,而閱讀的人往往看不出來。在回傳前先檢查答案,就能在 Agent 還能重試時抓出這些問題。

驗證器會將完成的答案,連同其依據的檔案與工具執行結果一起送給 Jev。Jev 會為答案品質打分,並判斷其中的主張是否 grounded(有根據),也就是是否有 Agent 實際讀取的內容作為佐證。

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};

有兩條規則可避免 Agent 無限重試:總共最多嘗試兩次;而當 Jev 對自己打的分数沒信心時,harness 會直接接受答案,而不是花錢再試一次。

安全性與日誌記錄

Jev 給你的是機率,而機率有可能出錯。因此,凡是能用一般程式碼明確檢查的事,都應該用程式碼檢查。在這個 harness 中,所有檔案工具都會拒絕專案資料夾以外的任何路徑,不管 Jev 怎麼說。把 Jev 留給程式碼無法做出的判斷。

把關機制只看 tool call 本身,也就是工具名稱與其輸入參數。這有助於防範 prompt injection(提示注入)——藏在檔案或網頁中的文字誘使模型做出有害行為。把關機制看不到那些欺騙手法,但仍能看到它所導致的有害呼叫。

請記錄每個決策及其背後的數字。日誌能解釋某件事為何被阻擋,並提供真實數據來設定閾值。這個 harness 會將每個決策寫成一行,存入名為 decisions.jsonl 的檔案。由於把關機制會看到 Agent 嘗試做的所有事,因此日誌會隱藏電子郵件地址與金鑰,並縮減過長的輸入。

動手試試

下方的沙盒會執行本教學完成的 harness,並連接真實的 Jev。它會在步道勘查筆記資料夾上運作,而其 delete_path 工具真的可以刪除這些檔案。

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

為什麼要自建 harness

現成的 Agent 是用內建規則來做這些決定,而自訂 harness 則把它們放進你的程式碼裡。由你挑選模型、設定閾值、決定何時需要人工介入,並能從日誌中清楚讀出每次呼叫的確切原因。

Jev 讓這一切變得可行。每個決策只需幾百毫秒,成本不到一美分的零頭,因此你可以在任何需要的地方加上檢查,而不僅限於負擔得起完整模型呼叫的地方。本教學的三個部分只是起點,針對你自己領域打造的自訂 harness,可以向 Jev 提出任何重要的問題。

其他用途

同樣的三個部分也能用在程式設計 Agent 之外。

  • 程式碼審查機器人。 為每個建議的修改評分,只把值得看的顯示給人看。
  • 資料清理。 合併前先詢問兩筆紀錄是否描述同一件事,把不確定的配對留給人工處理。
  • 文件處理流程。 為每個擷取出來的頁面評分,只重新處理分數低的。
  • 核准佇列。 只有落在「需人工確認」區間的呼叫才會送到人類審查者手上。

在上述每種情況中,語言模型負責開放式的核心工作,而 Jev 回答周邊的小問題。

本教學中的問題、閾值與 policy 都是供學習用的範例,並非調校過的正式環境設定。我們正在測試這些 harness 改動對成本與答案品質的影響,後續很快會推出公布結果的指南。

我花了一個晚上和 Opus 5.5 一起完成這份指南與沙盒。如果你遇到任何問題,歡迎私訊我。也請隨意複製這篇文章餵給你的 Agents,繼續實驗這些想法。

一鍵儲存

使用 YouMind AI 深度閱讀爆款文章

保存原文、追問細節、總結觀點,並在一個 AI 工作空間裡把爆款文章沉澱成可複用筆記。

了解 YouMind
寫給創作者

把你的 Markdown 變成乾淨的 𝕏 文章

圖片上傳、表格、程式碼區塊,往 𝕏 上手動重排太痛苦。YouMind 把整篇 Markdown 一鍵轉成乾淨、可直接發佈的 𝕏 文章草稿。

試試 Markdown 轉 𝕏

更多可拆解樣本

近期爆款文章

探索更多爆款文章