Skip to main content

lib/cmdc_eval/suite.ex

defmodule CMDCEval.Suite do
  @moduledoc """
  Eval Suite behaviour —— 每个 Suite(如 BFCL / tau2-bench / internal)实现 3 callback。

  ## 必须实现的 3 个 callback

  ### `name/0`

  返回 Suite 唯一标识(atom 或 string,进入 Report.suite_name)。

  ### `cases/0`

  返回 `[CMDCEval.Case.t()]` 列表,Runner 按 case 并发跑。

  ### `assert/2`

  传入 `(case, agent_reply_text)`,返回 `boolean()` 表示该 case 是否通过。

  ## 可选回调

  ### `default_tools/0`

  返回该 Suite 全局默认工具模块列表(被 Case `:tools` 覆盖)。默认 `[]`。

  ### `cost_estimator/1`

  传入 `%{model, tokens_in, tokens_out}` 返回估算 cost_usd。默认返回 `0.0`。

  ## 示例实现

      defmodule MyApp.MyEvalSuite do
        @behaviour CMDCEval.Suite

        alias CMDCEval.Case

        @impl true
        def name, do: "my_eval"

        @impl true
        def cases do
          [
            Case.new(id: "basic", input: "1 + 1 = ?", expected: ~r/2/),
            Case.new(id: "tool_call", input: "read README.md",
                     expected: %{tool_called: "read_file"})
          ]
        end

        @impl true
        def assert(%Case{expected: %Regex{} = re}, reply), do: Regex.match?(re, reply)
        def assert(%Case{expected: %{tool_called: name}}, _reply) do
          # 检查 Agent 是否调用了某工具(需要 events 数据,由 Runner 透传)
          # 这里简化为 string match
          String.contains?(reply, name)
        end
      end
  """

  alias CMDCEval.Case

  @callback name() :: String.t() | atom()
  @callback cases() :: [Case.t()]
  @callback assert(Case.t(), reply :: String.t()) :: boolean()

  @callback default_tools() :: [module()]
  @callback cost_estimator(map()) :: float()

  @optional_callbacks default_tools: 0, cost_estimator: 1
end