> Discover all available pages from the documentation index: https://mastra.zisheng.pro/ko/llms.txt # 다중 턴 평가 다중 턴 평가는 대화 전반에 걸쳐 Agent 동작을 테스트합니다. 싱글 대신`input`, 당신은`inputs`정렬. 각 항목은 동일한 스레드의 Agent에 순차적으로 전송되며 득점자는 모든 턴에서 누적된 출력을 확인합니다. ## 다중 턴 평가를 사용하는 경우 - Agent는 Memory를 사용하고 이전 턴의 컨텍스트를 기억해야 합니다. - Agent은 이전 응답에 따라 후속 질문을 처리합니다. - 대화 전체에서 Tool 호출 순서를 확인해야 합니다. - Agent는 다단계 Workflow(검색, 확인, 실행)를 실행합니다. ## 빠른 시작 ```typescript 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가 필요합니다. 멀티 턴 회상은 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`가 관리하므로 직접 제공하지 않습니다. ```typescript 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](https://mastra.zisheng.pro/ko/docs/memory/overview)를 참조하세요. ## 작동 원리 데이터 항목에`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` `inputs` 형식은 **누적된** 출력 전체, 즉 모든 턴의 출력에 단일 점수를 매깁니다. 이 방식은 턴별 실패를 숨길 수 있습니다. `checks.includes('Brooklyn')` 같은 출력 기반 검사는 후속 턴이 잘못되었더라도 _어느_ 턴에서든 Brooklyn을 언급하면 통과합니다. **특정 턴**이 올바르게 동작했는지 확인해야 한다면 대신 `turns`를 사용하세요. 각 턴은 자체 `input`과 선택적 `gates`/`scorers`를 가진 객체이며, **해당 턴만의** 입력과 출력을 평가합니다. ```typescript 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.input`과 `run.output`을 확인하며 누적된 대화는 확인하지 않습니다. 이 방식은 잘못된 턴이 검사를 충족할 수 없고 각 턴에 올바른 `run.input`이 사용되므로 `inputs`의 두 가지 사각지대를 모두 해결합니다. - 턴별 결과는 [판정](https://mastra.zisheng.pro/ko/docs/evals/gates-and-verdicts)에 반영됩니다. 턴의 게이트가 실패하면 판정은 `failed`가 됩니다. 게이트는 통과했지만 턴의 임계값을 충족하지 못하면 `scored`가 됩니다. - `result.turnResults[i]`는 각 턴의 `gateResults`, `thresholdResults`, `scores`를 보고하므로 정확히 어느 턴에서 실패했는지 알 수 있습니다. 대화가 여러 개라면 턴 인덱스별로 결과의 평균을 계산합니다. - `gates` 또는 `scorers`가 없는 턴도 대화를 진행합니다. - 최상위 `scorers`/`gates`는 여전히 누적된 출력 전체에 대해 실행되므로 "이 턴에서는 반드시 Tool을 호출해야 함"과 "답변에서 Brooklyn을 언급해야 함"을 함께 사용할 수 있습니다. - Agent에 스토리지가 구성되어 있다면 턴별 채점자/게이트 결과도 최상위 점수와 마찬가지로 유지되므로 점수 스토어에 나타납니다. 저장된 각 턴별 점수에는 해당 턴 인덱스(`metadata.turnIndex`)가 포함되고, 대화의 `threadId`를 공유하며, 해당 턴의 자체 Trace 범위에 연결됩니다. 전체 대화에 대한 단일 점수면 충분할 때는 `inputs`를 사용하세요. 개별 턴의 정확성이 중요할 때는 `turns`를 사용하세요. 같은 데이터 항목에서 `turns`를 `input` 또는 `inputs`와 함께 사용할 수 없습니다. ## 게이트 및 임계값과 결합 멀티 턴 데이터 항목은 [게이트 및 판정](https://mastra.zisheng.pro/ko/docs/evals/gates-and-verdicts)과 함께 사용할 수 있습니다. 게이트가 전체 대화를 반영하도록 출력 기반 채점자를 사용하세요. ```typescript 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` 호출에 단일 턴과 멀티 턴 데이터 항목을 모두 포함할 수 있습니다. ```typescript 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`를 발생시킵니다. ```typescript // Throws: 'inputs' must be a non-empty array await runEvals({ data: [{ inputs: [] }], target: myAgent, scorers: [myScorer], }) ``` ## 관련된 - [`runEvals()` 레퍼런스](https://mastra.zisheng.pro/ko/reference/evals/run-evals): 전체 `runEvals` 매개변수 및 반환 값 - [게이트 및 판정](https://mastra.zisheng.pro/ko/docs/evals/gates-and-verdicts): 엄격한 요구 사항 및 품질 임계값 적용 - [빠른 검사](https://mastra.zisheng.pro/ko/docs/evals/quick-checks): 구성 가능한 Zero-LLM 마이크로 채점자