본문으로 건너뛰기

다중 턴 평가

다중 턴 평가는 대화 전반에 걸쳐 Agent 동작을 테스트합니다. 싱글 대신input, 당신은inputs정렬. 각 항목은 동일한 스레드의 Agent에 순차적으로 전송되며 득점자는 모든 턴에서 누적된 출력을 확인합니다.

다중 턴 평가를 사용하는 경우
다중 턴 평가를 사용하는 경우에 대한 직접 링크

  • Agent는 Memory를 사용하고 이전 턴의 컨텍스트를 기억해야 합니다.
  • 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')],
})

각 턴은 같은 스레드 ID로 agent.generate()를 실행하므로 Agent는 전체 대화 기록을 볼 수 있습니다. 채점자는 모든 턴에서 누적된 출력 메시지를 받습니다.

교차 호출을 위해서는 Memory가 필요합니다.
교차 호출을 위해서는 Memory가 필요합니다.에 대한 직접 링크

멀티 턴 회상은 Agent에 Memory 스토어가 구성되어 있는지에 따라 달라집니다. 공유 스레드 ID 덕분에 각 턴에서 이전 턴을 볼 수 있지만, Agent에 Memory가 있어야 스레드에 기록이 유지됩니다. Agent에 Memory가 구성되어 있지 않아도 턴은 순서대로 실행되고 출력은 채점을 위해 계속 누적되지만, Agent는 이전 턴을 기억하지 못합니다(각 입력이 독립적으로 실행됨). Memory가 없는 Agent에서 inputs를 사용하면 runEvals가 경고를 기록합니다. runEvals는 대화 ID를 관리합니다. 공유 threadId를 생성하고 resourceId를 주입합니다(Mastra Memory는 resource + thread를 기준으로 메시지 범위를 지정하므로 회상이 작동하려면 둘 다 필요함). 기본적으로 리소스는 생성된 스레드에서 파생되므로 각 대화가 격리됩니다. 예를 들어 기존 사용자의 Memory를 재사용하기 위해 특정 리소스를 고정하려면 targetOptions.memory.resource를 전달하세요. 스레드는 여전히 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' } },
})

Memory 스토어 구성 방법은 Memory를 참조하세요.

작동 원리
작동 원리에 대한 직접 링크

데이터 항목에inputs array, runEvals:

  1. 대화를 위한 새 스레드(고유한 threadId)와 리소스를 생성합니다(호출자가 제공한 targetOptions.memory.resource는 유지됨).
  2. 해당 스레드에서 agent.generate()를 통해 각 입력을 순차적으로 전송합니다.
  3. 모든 턴의 출력 메시지를 누적합니다.
  4. 평가를 위해 누적된 전체 출력을 채점자에게 전달합니다. 득점자는 모든 턴을 포함하여 전체 대화 결과를 봅니다.

점수 의미론
점수 의미론에 대한 직접 링크

다중 회전 항목에 대한 채점자를 작성할 때 이러한 세부 사항이 중요합니다.

  • run.output은 각 턴에서 누적된 출력입니다. 출력 기반 채점자인 checks.includes, checks.calledTool, checks.similarity 등은 전체 대화를 평가합니다. 예를 들어 checks.calledTool('get_weather', { times: 2 })는 모든 턴의 호출 횟수를 계산합니다.
  • run.input은 첫 번째 턴의 입력일 뿐입니다. 입력과 출력을 비교하는 채점자(충실도, 답변 관련성 및 기타 입력 관련 LLM 채점자)는 전체 대화가 아니라 첫 번째 사용자 메시지만 볼 수 있습니다. 멀티 턴에서는 출력 기반 검사를 사용하거나 누적된 run.output을 직접 읽는 채점자를 만드세요.

턴별 어설션turns
per-turn-assertions-with-turns에 대한 직접 링크

inputs 형식은 누적된 출력 전체, 즉 모든 턴의 출력에 단일 점수를 매깁니다. 이 방식은 턴별 실패를 숨길 수 있습니다. checks.includes('Brooklyn') 같은 출력 기반 검사는 후속 턴이 잘못되었더라도 어느 턴에서든 Brooklyn을 언급하면 통과합니다. 특정 턴이 올바르게 동작했는지 확인해야 한다면 대신 turns를 사용하세요. 각 턴은 자체 input과 선택적 gates/scorers를 가진 객체이며, 해당 턴만의 입력과 출력을 평가합니다.

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을 확인하며 누적된 대화는 확인하지 않습니다. 이 방식은 잘못된 턴이 검사를 충족할 수 없고 각 턴에 올바른 run.input이 사용되므로 inputs의 두 가지 사각지대를 모두 해결합니다.
  • 턴별 결과는 판정에 반영됩니다. 턴의 게이트가 실패하면 판정은 failed가 됩니다. 게이트는 통과했지만 턴의 임계값을 충족하지 못하면 scored가 됩니다.
  • result.turnResults[i]는 각 턴의 gateResults, thresholdResults, scores를 보고하므로 정확히 어느 턴에서 실패했는지 알 수 있습니다. 대화가 여러 개라면 턴 인덱스별로 결과의 평균을 계산합니다.
  • gates 또는 scorers가 없는 턴도 대화를 진행합니다.
  • 최상위 scorers/gates는 여전히 누적된 출력 전체에 대해 실행되므로 "이 턴에서는 반드시 Tool을 호출해야 함"과 "답변에서 Brooklyn을 언급해야 함"을 함께 사용할 수 있습니다.
  • Agent에 스토리지가 구성되어 있다면 턴별 채점자/게이트 결과도 최상위 점수와 마찬가지로 유지되므로 점수 스토어에 나타납니다. 저장된 각 턴별 점수에는 해당 턴 인덱스(metadata.turnIndex)가 포함되고, 대화의 threadId를 공유하며, 해당 턴의 자체 Trace 범위에 연결됩니다. 전체 대화에 대한 단일 점수면 충분할 때는 inputs를 사용하세요. 개별 턴의 정확성이 중요할 때는 turns를 사용하세요. 같은 데이터 항목에서 turnsinput 또는 inputs와 함께 사용할 수 없습니다.

게이트 및 임계값과 결합
게이트 및 임계값과 결합에 대한 직접 링크

멀티 턴 데이터 항목은 게이트 및 판정과 함께 사용할 수 있습니다. 게이트가 전체 대화를 반영하도록 출력 기반 채점자를 사용하세요.

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가 존재하지만 비어 있으면 runEvalsMastraError를 발생시킵니다.

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