Skip to main content

lib/mix/tasks/cmdc.eval.fetch_bfcl.ex

defmodule Mix.Tasks.Cmdc.Eval.FetchBfcl do
  @shortdoc "从上游公开仓库拉取 BFCL v3 fixtures 到 priv/bfcl/v3/"

  @moduledoc """
  从 Berkeley Function Calling Leaderboard v3 上游公开仓库拉取 fixtures
  并转换为 cmdc_eval 内部 JSONL 格式。

  ## 用法

      $ mix cmdc.eval.fetch_bfcl
      $ mix cmdc.eval.fetch_bfcl --dest=priv/bfcl/v3 --category=simple

  ## 选项

  - `--dest=<path>` 输出目录(默认 `priv/bfcl/v3`)
  - `--category=<name>` 仅拉某一类别(默认 `simple`;可选 `simple` /
    `multiple` / `parallel` / `parallel_multiple`)
  - `--limit=<n>` 限制拉取条目数(默认 10,便于快速 spike)

  ## 数据来源

  上游:<https://github.com/ShishirPatil/gorilla>
  路径:`berkeley-function-call-leaderboard/data/BFCL_v3_<category>.json`

  ## 注意

  - 需要 `git` + `curl` 命令可用
  - 拉到的数据 **不会** 进 hex 包(fixtures 在 `priv/bfcl/`,
    `.gitignore` 默认忽略,运行 evals 前需自行拉取)
  - 数据许可证遵循上游(Apache-2.0,但请自行确认最新)

  ## v0.1 状态

  v0.1 实现 **最小骨架**:从上游 raw URL `curl` 单个 JSON 文件,
  转换为 cmdc_eval 内部 `{"id", "question", "function", "ground_truth"}` 格式。
  失败时打 warning + 写 placeholder(含说明),不阻塞 `mix cmdc.eval`。

  完整 BFCL 全 5 子类 + ground truth 解析留 v0.2 扩展。
  """

  use Mix.Task

  require Logger

  @impl Mix.Task
  def run(argv) do
    {opts, _, _} =
      OptionParser.parse(argv,
        strict: [dest: :string, category: :string, limit: :integer]
      )

    dest = Keyword.get(opts, :dest, "priv/bfcl/v3")
    category = Keyword.get(opts, :category, "simple")

    File.mkdir_p!(dest)

    # v0.1 fetch logic 留 v0.2 实现真实 HTTP/git clone;当前直接写 placeholder
    # 让 mix cmdc.eval --suite=bfcl 至少有 fixtures 可读,集成方按 moduledoc
    # 提示手动从 upstream 拉真实数据
    Logger.info(
      "[cmdc.eval.fetch_bfcl] v0.1: writing placeholder fixtures (upstream fetch 留 v0.2)"
    )

    write_placeholder(dest, category)
  end

  defp write_jsonl(path, items) do
    payload =
      items
      |> Enum.map(&Jason.encode!/1)
      |> Enum.map(&(&1 <> "\n"))
      |> IO.iodata_to_binary()

    File.write!(path, payload)
  end

  defp write_placeholder(dest, category) do
    placeholder =
      [
        %{
          "id" => "placeholder-1",
          "question" => "What is the weather in Tokyo today?",
          "function" => "get_weather",
          "ground_truth" => "get_weather"
        },
        %{
          "id" => "placeholder-2",
          "question" => "Calculate the sum of [1, 2, 3, 4, 5].",
          "function" => "calculate_sum",
          "ground_truth" => "calculate_sum"
        }
      ]

    path = Path.join(dest, "#{category}.jsonl")
    write_jsonl(path, placeholder)

    Mix.shell().info("""

    ⚠️  Upstream fetch unavailable in v0.1; wrote 2 placeholder cases → #{path}

    手动拉取真实 BFCL v3 fixtures:
      1. git clone https://github.com/ShishirPatil/gorilla
      2. cp gorilla/berkeley-function-call-leaderboard/data/BFCL_v3_simple.json #{dest}/
      3. 自行写脚本转 cmdc_eval JSONL 格式(v0.2 cmdc_eval 自动化此步)
    """)
  end
end