跳至主要內容

多輪 Evals

多輪 Evals 會測試 Agent 在一段對話中的行為。你不必提供單一 input,而是提供 inputs 陣列。每個項目都會在同一個 thread 上依序傳送給 Agent,評分器則會看到所有對話輪次累積的輸出。

多輪 Evals 的適用時機
「多輪 Evals 的適用時機」的直接連結

  • Agent 使用記憶體,而且必須回想先前對話輪次的內容
  • Agent 會處理依賴先前回應的後續問題
  • 你需要驗證對話中的 Tool 呼叫順序
  • Agent 會執行多步驟 Workflow(搜尋、確認、執行)

快速入門
「快速入門」的直接連結

src/evals/conversation-eval.ts
import { runEvals } from '@mastra/core/evals'
import { checks } from '@mastra/evals/checks'
import { weatherAgent } from '../agents'

const result = await runEvals({
data: [
{
inputs: [
'What is the weather in Brooklyn?',
'What about tomorrow?',
'Compare the two forecasts.',
],
},
],
target: weatherAgent,
scorers: [checks.calledTool('get_weather', { times: 2 }), checks.includes('Brooklyn')],
})

每個對話輪次都會使用相同的 thread ID 執行 agent.generate(),因此 Agent 能看到完整對話記錄。評分器會收到所有對話輪次累積的輸出訊息。

跨輪回想需要記憶體
「跨輪回想需要記憶體」的直接連結

多輪回想需要 Agent 已設定記憶體儲存區。共用 thread ID 可讓每個對話輪次看到先前輪次,但只有 Agent 設有記憶體時,thread 才會保存記錄。若 Agent 未設定記憶體,對話輪次仍會依序執行,輸出也仍會累積供評分使用,但 Agent 不會記得先前輪次(每筆輸入都會獨立執行)。當你對未設定記憶體的 Agent 使用 inputs 時,runEvals 會記錄警告。

runEvals 會替你管理對話識別資訊:它會產生共用 threadId 並注入 resourceId(Mastra 記憶體會依資源與 thread 界定訊息範圍,因此兩者都是回想功能的必要條件)。資源預設衍生自產生的 thread,讓每段對話彼此隔離。若要固定特定資源,例如重複使用現有使用者的記憶體,請傳入 targetOptions.memory.resource;thread 仍由 runEvals 管理,因此不必提供:

await runEvals({
target: weatherAgent,
data: [{ inputs: ['What is the weather in Brooklyn?', 'What about tomorrow?'] }],
scorers: [checks.similarity('weather forecast')],
targetOptions: { memory: { resource: 'user-42' } },
})

如需記憶體儲存區的設定方式,請參閱記憶體

運作方式
「運作方式」的直接連結

當資料項目含有 inputs 陣列時,runEvals 會:

  1. 為對話建立新的 thread(不重複的 threadId)與資源(會保留呼叫端提供的 targetOptions.memory.resource
  2. 在該 thread 上透過 agent.generate() 依序傳送每筆輸入
  3. 累積所有對話輪次的輸出訊息
  4. 將完整的累積輸出傳給評分器進行評估

評分器會看到完整對話輸出,包括每個對話輪次。

評分語意
「評分語意」的直接連結

為多輪項目撰寫評分器時,請注意以下細節:

  • run.output 是每個對話輪次的累積輸出。 輸出式評分器(如 checks.includeschecks.calledToolchecks.similarity 等)會評估整段對話。例如,checks.calledTool('get_weather', { times: 2 }) 會計算所有對話輪次中的呼叫次數。
  • run.input 只包含第一輪輸入。 比較輸入與輸出的評分器(忠實度、答案相關性與其他輸入相對式 LLM 評分器)只會看到第一則使用者訊息,而非完整對話。對多輪評估而言,請優先使用輸出式檢查,或建立直接讀取累積 run.output 的評分器。

使用 turns 進行逐輪斷言
「per-turn-assertions-with-turns」的直接連結

inputs 形式會將累積輸出視為整體評分,也就是針對所有對話輪次的輸出給出單一分數。這可能掩蓋個別輪次的失敗:只要_任一_輪提到 Brooklyn,checks.includes('Brooklyn') 之類的輸出式檢查就會通過,即使後續輪次已出錯也是如此。

若要斷言特定對話輪次的行為正確,請改用 turns。每個對話輪次都是一個物件,包含自己的 input,並可選擇提供只評估該輪輸入與輸出的 gatesscorers

src/evals/per-turn-eval.ts
import { runEvals } from '@mastra/core/evals'
import { checks } from '@mastra/evals/checks'
import { weatherAgent } from '../agents'

const result = await runEvals({
data: [
{
turns: [
{
input: 'What is the weather in Brooklyn?',
gates: [checks.calledTool('get_weather')],
},
{
// The follow-up must call the tool again — it can't be satisfied
// by the first turn's tool call.
input: 'What about tomorrow?',
gates: [checks.calledTool('get_weather')],
scorers: [{ scorer: checks.similarity('tomorrow forecast'), threshold: 0.5 }],
},
],
},
],
target: weatherAgent,
})

result.verdict // 'passed' | 'scored' | 'failed'
result.turnResults // per-turn gate/threshold/scorer outcomes

語意如下:

  • 逐輪閘門或評分器只會看到該輪的 run.inputrun.output,絕不會看到累積對話。這解決了 inputs 的兩個盲點:錯誤的對話輪次無法滿足檢查,而且每一輪的 run.input 都正確無誤。
  • 逐輪結果會納入判定結果:某一輪的閘門失敗會使判定結果成為 failed;若閘門皆通過,但某輪未達臨界值,則結果為 scored
  • result.turnResults[i] 會回報各輪的 gateResultsthresholdResultsscores,因此能精確指出失敗的對話輪次。若有多段對話,則依對話輪次索引計算平均結果。
  • 未設定 gatesscorers 的對話輪次仍會推進對話。
  • 頂層 scorersgates 仍會針對累積輸出整體執行,因此你可以結合「這一輪必須呼叫 Tool」與「答案提到 Brooklyn」等條件。
  • Agent 設有儲存區時,每個逐輪評分器/閘門結果都會像頂層分數一樣保存,因此逐輪結果會出現在分數儲存區中。每個已儲存的逐輪分數都會標示其對話輪次索引(metadata.turnIndex)、共用該段對話的 threadId,並連結至該輪自己的 Trace span。

若整段對話只需一個整體分數,請使用 inputs;若正確性取決於個別對話輪次,請使用 turns。同一資料項目中的 turns 不能與 inputinputs 結合使用。

結合閘門與臨界值
「結合閘門與臨界值」的直接連結

多輪資料項目可搭配閘門與判定結果使用。請使用輸出式評分器,讓閘門判斷反映完整對話:

src/evals/memory-eval.ts
import { runEvals } from '@mastra/core/evals'
import { checks } from '@mastra/evals/checks'

const result = await runEvals({
data: [
{
inputs: ['My favorite city is Brooklyn.', 'What is the weather in my favorite city?'],
},
],
target: weatherAgent,
gates: [checks.calledTool('get_weather')],
scorers: [{ scorer: checks.similarity('Brooklyn weather forecast'), threshold: 0.5 }],
})

result.verdict // 'passed' | 'scored' | 'failed'

混用單輪與多輪項目
「混用單輪與多輪項目」的直接連結

單次 runEvals 呼叫可以同時包含單輪與多輪資料項目:

src/evals/mixed-eval.ts
const result = await runEvals({
data: [
{ input: 'What is the weather in Brooklyn?' },
{
inputs: ['My favorite city is Brooklyn.', 'What is the weather in my favorite city?'],
},
],
target: weatherAgent,
scorers: [checks.includes('Brooklyn')],
})

單輪項目照常使用 input。多輪項目使用 inputs,可完全省略 input

驗證
「驗證」的直接連結

inputs 存在但為空,runEvals 會擲回 MastraError

// Throws: 'inputs' must be a non-empty array
await runEvals({
data: [{ inputs: [] }],
target: myAgent,
scorers: [myScorer],
})