e-mon Claude Opus 4.8 (1M context) commited on
Commit
88e4403
·
1 Parent(s): d5440a0

fix(upstream): add multi-layer cache to survive GitHub raw 429

Browse files

起動時に GitHub raw から all_datasets.yaml を直接取得しており、
raw.githubusercontent.com の 429 (rate limit) でアプリ起動が
クラッシュしていた。取得を多層フォールバック化して耐障害性を確保する。

- GitHub raw を指数バックオフで最大3回リトライ
- 429 等で失敗したら jsDelivr CDN ミラーへフォールバック
- ネットワーク取得成功分をローカル永続キャッシュへ退避し再利用
- コールドスタート用にリポジトリ同梱フォールバック (src/data) を追加
- 実行時生成キャッシュ /upstream/ を .gitignore に追加

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files changed (4) hide show
  1. .gitignore +3 -0
  2. src/data/all_datasets.yaml +227 -0
  3. src/envs.py +13 -4
  4. src/upstream.py +118 -6
.gitignore CHANGED
@@ -12,3 +12,6 @@ eval-results/
12
  eval-queue-bk/
13
  eval-results-bk/
14
  logs/
 
 
 
 
12
  eval-queue-bk/
13
  eval-results-bk/
14
  logs/
15
+
16
+ # upstream YAML の永続キャッシュ (実行時生成)
17
+ /upstream/
src/data/all_datasets.yaml ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ datasets:
2
+ - aime2024
3
+ - aime2025
4
+ - aio
5
+ - alt-j-to-e
6
+ - alt-e-to-j
7
+ - bigbenchhard_direct
8
+ - bigbenchhard_cot
9
+ - bigbenchhard_ja_direct
10
+ - bigbenchhard_ja_cot
11
+ - chabsa
12
+ - commonsensemoralja
13
+ - drop
14
+ - gsm8k
15
+ - gpqa_diamond_en
16
+ - gpqa_extended_en
17
+ - gpqa_main_en
18
+ - gpqa_diamond_ja
19
+ - gpqa_extended_ja
20
+ - gpqa_main_ja
21
+ - jamc-qa
22
+ - jculture-mcq
23
+ - jamp
24
+ - janli
25
+ - jcommonsenseqa
26
+ - jemhopqa
27
+ - jfinqa
28
+ - jhumaneval
29
+ - jmmlu
30
+ - jnli
31
+ - jsem
32
+ - jsick
33
+ - jsquad
34
+ - jsts
35
+ - kuci
36
+ - mawps
37
+ - mbpp
38
+ - mgsm
39
+ - mmlu_en
40
+ - mmlu_prox_ja
41
+ - mmlu_prox_en
42
+ - mif_eval_ja
43
+ - mif_eval_en
44
+ - mmmlu
45
+ - niilc
46
+ - openbookqa
47
+ - polymath-en
48
+ - polymath-ja
49
+ - structeval
50
+ - triviaqa
51
+ - winogrande_xl
52
+ - wiki_coreference
53
+ - wiki_dependency
54
+ - wiki_ner
55
+ - wiki_pas
56
+ - wiki_reading
57
+ - wikicorpus-j-to-e
58
+ - wikicorpus-e-to-j
59
+ - xlsum_ja
60
+ - hle
61
+ - jhle
62
+ - jblimp
63
+ - jcola-in-domain
64
+ - jcola-out-of-domain
65
+ categories:
66
+ NLI:
67
+ description: "Natural Language Inference"
68
+ default_metric: exact_match
69
+ metrics: {}
70
+ datasets:
71
+ - jamp
72
+ - janli
73
+ - jnli
74
+ - jsem
75
+ - jsick
76
+ QA:
77
+ description: "Question Answering"
78
+ default_metric: exact_match
79
+ metrics:
80
+ triviaqa: triviaqa_f1
81
+ drop: drop_f1
82
+ jculture-mcq: set_f1
83
+ datasets:
84
+ - jemhopqa
85
+ - niilc
86
+ - aio
87
+ - triviaqa
88
+ - drop
89
+ - jamc-qa
90
+ - jculture-mcq
91
+ RC:
92
+ description: "Reading Comprehension"
93
+ default_metric: exact_match
94
+ metrics: {}
95
+ datasets:
96
+ - jsquad
97
+ CR:
98
+ description: "Commonsense Reasoning"
99
+ default_metric: exact_match
100
+ metrics: {}
101
+ datasets:
102
+ - jcommonsenseqa
103
+ - commonsensemoralja
104
+ - kuci
105
+ - winogrande_xl
106
+ HE-JA:
107
+ description: "Human Evaluation"
108
+ default_metric: exact_match
109
+ metrics:
110
+ jhle: hle_exact_match
111
+ datasets:
112
+ - mmmlu
113
+ - jmmlu
114
+ - mmlu_prox_ja
115
+ - gpqa_diamond_ja
116
+ - gpqa_extended_ja
117
+ - gpqa_main_ja
118
+ - jhle
119
+ HE-EN:
120
+ description: "Human Evaluation"
121
+ default_metric: exact_match
122
+ metrics:
123
+ hle: hle_exact_match
124
+ datasets:
125
+ - mmlu_en
126
+ - mmlu_prox_en
127
+ - openbookqa
128
+ - gpqa_diamond_en
129
+ - gpqa_extended_en
130
+ - gpqa_main_en
131
+ - hle
132
+ EL:
133
+ description: "Entity Linking"
134
+ default_metric: set_f1
135
+ metrics: {}
136
+ datasets:
137
+ - chabsa
138
+ FA:
139
+ description: "Fine-grained Analysis"
140
+ default_metric: set_f1
141
+ metrics:
142
+ wiki_reading: char_f1
143
+ datasets:
144
+ - wiki_ner
145
+ - wiki_dependency
146
+ - wiki_pas
147
+ - wiki_coreference
148
+ - wiki_reading
149
+ MR:
150
+ description: "Math Reasoning"
151
+ default_metric: mathematical_equivalence
152
+ metrics:
153
+ polymath-en: polymath_weighted_accuracy
154
+ polymath-ja: polymath_weighted_accuracy
155
+ datasets:
156
+ - aime2024
157
+ - aime2025
158
+ - gsm8k
159
+ - jfinqa
160
+ - mawps
161
+ - mgsm
162
+ - polymath-en
163
+ - polymath-ja
164
+ MT:
165
+ description: "Machine Translation"
166
+ default_metric: comet_wmt22
167
+ metrics: {}
168
+ datasets:
169
+ - alt-e-to-j
170
+ - alt-j-to-e
171
+ - wikicorpus-e-to-j
172
+ - wikicorpus-j-to-e
173
+ CG:
174
+ description: "Code Generation"
175
+ default_metric: code_exec_sandbox
176
+ metrics: {}
177
+ datasets:
178
+ - mbpp
179
+ - jhumaneval
180
+ SUM:
181
+ description: "Summarization"
182
+ default_metric: rouge2_scaling
183
+ metrics: {}
184
+ datasets:
185
+ - xlsum_ja
186
+ IF:
187
+ description: "Instruction Following"
188
+ default_metric: mifeval_strict
189
+ metrics:
190
+ structeval: structeval
191
+ datasets:
192
+ - mif_eval_ja
193
+ - mif_eval_en
194
+ - structeval
195
+ BBH:
196
+ description: "BIG-Bench Hard"
197
+ default_metric: exact_match
198
+ metrics: {}
199
+ datasets:
200
+ - bigbenchhard_direct
201
+ - bigbenchhard_cot
202
+ - bigbenchhard_ja_direct
203
+ - bigbenchhard_ja_cot
204
+ LM:
205
+ description: "Language Modeling"
206
+ default_metric: exact_match
207
+ metrics: {}
208
+ datasets:
209
+ - jblimp
210
+ - jcola-in-domain
211
+ - jcola-out-of-domain
212
+ dataset_info_overrides: {}
213
+ # you can override the below attributes.
214
+ # instruction: str
215
+ # output_length: int
216
+ # metrics: list[str]
217
+ # samples: list[Sample]
218
+ # label_list: list[str] = field(default_factory=list)
219
+ # answer_extract_pattern: Optional[str] = ""
220
+ # num_few_shots: int | None = None
221
+ # answer_pattern_id: AnswerPatternId = AnswerPatternId.CUSTOM
222
+ # language: Literal["ja", "en"] = "ja"
223
+ # custom_prompt_template: Optional[str] = None
224
+ # for example, revise `dataset_info_overrides: {}` to `dataset_info_overrides:`, and write the below.
225
+ # jamp:
226
+ # answer_pattern_id: AnswerPatternId = AnswerPatternId.CUSTOM
227
+ # num_few_shots: 0
src/envs.py CHANGED
@@ -22,9 +22,18 @@ EVAL_REQUESTS_PATH = CACHE_PATH / "eval-queue"
22
 
23
  # llm-jp-eval upstream の dataset 定義 (dev branch を直接参照することで
24
  # 新 dataset が upstream にマージされた瞬間にフロントが認識する)
25
- LLM_JP_EVAL_DATASETS_URL = (
26
- "https://raw.githubusercontent.com/llm-jp/llm-jp-eval/"
27
- "dev/eval_configs/all_datasets.yaml"
28
- )
 
 
 
 
 
 
 
 
 
29
 
30
  API = HfApi(token=HF_TOKEN)
 
22
 
23
  # llm-jp-eval upstream の dataset 定義 (dev branch を直接参照することで
24
  # 新 dataset が upstream にマージされた瞬間にフロントが認識する)
25
+ LLM_JP_EVAL_DATASETS_URL = "https://raw.githubusercontent.com/llm-jp/llm-jp-eval/dev/eval_configs/all_datasets.yaml"
26
+
27
+ # raw.githubusercontent.com が 429 (rate limit) を返した際の CDN ミラー。
28
+ # jsDelivr は GitHub を長期キャッシュ配信するため rate limit を回避できる。
29
+ LLM_JP_EVAL_DATASETS_MIRROR_URL = "https://cdn.jsdelivr.net/gh/llm-jp/llm-jp-eval@dev/eval_configs/all_datasets.yaml"
30
+
31
+ # ネットワーク取得成功時に内容を退避する永続キャッシュ。次回以降 upstream/
32
+ # ミラーがともに落ちていても、直近取得分でフロントを起動できる。
33
+ LLM_JP_EVAL_DATASETS_CACHE_PATH = CACHE_PATH / "upstream" / "all_datasets.yaml"
34
+
35
+ # リポジトリ同梱の最終フォールバック。キャッシュが未生成のコールドスタートでも
36
+ # 起動を保証する (取得成功時に上書きはしない)。
37
+ LLM_JP_EVAL_DATASETS_FALLBACK_PATH = pathlib.Path(__file__).parent / "data" / "all_datasets.yaml"
38
 
39
  API = HfApi(token=HF_TOKEN)
src/upstream.py CHANGED
@@ -1,11 +1,110 @@
1
- """llm-jp-eval upstream から all_datasets.yaml を取得するモジュール。"""
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  import functools
 
 
4
 
5
  import requests
6
  import yaml
7
 
8
- from src.envs import LLM_JP_EVAL_DATASETS_URL
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
 
11
  @functools.lru_cache(maxsize=1)
@@ -13,11 +112,24 @@ def load_upstream_eval_config() -> dict:
13
  """llm-jp-eval upstream から all_datasets.yaml の全内容を取得して返す。
14
 
15
  categories / datasets / dataset_info_overrides を含む dict。
16
- プロセス起動中は lru_cache で memoize する。
 
 
 
 
 
 
 
 
17
  """
18
- response = requests.get(LLM_JP_EVAL_DATASETS_URL, timeout=10)
19
- response.raise_for_status()
20
- return yaml.safe_load(response.text)
 
 
 
 
 
21
 
22
 
23
  def load_canonical_dataset_set() -> frozenset[str]:
 
1
+ """llm-jp-eval upstream から all_datasets.yaml を取得するモジュール。
2
+
3
+ 取得は多層フォールバックで冗長化しており、次の優先順位で最初に成功したものを
4
+ 採用する:
5
+
6
+ 1. GitHub raw (最新の dev branch) — リトライ付き
7
+ 2. jsDelivr CDN ミラー — raw が 429 (rate limit) の際の回避経路
8
+ 3. ローカル永続キャッシュ — 直近のネットワーク取得成功分
9
+ 4. リポジトリ同梱フォールバック — コールドスタート時の最終手段
10
+
11
+ いずれかのネットワーク取得に成功した時点で内容を永続キャッシュに退避するため、
12
+ 以降 upstream / ミラーが同時に落ちていてもフロントの起動失敗を防げる。
13
+ """
14
 
15
  import functools
16
+ import logging
17
+ import time
18
 
19
  import requests
20
  import yaml
21
 
22
+ from src.envs import (
23
+ LLM_JP_EVAL_DATASETS_CACHE_PATH,
24
+ LLM_JP_EVAL_DATASETS_FALLBACK_PATH,
25
+ LLM_JP_EVAL_DATASETS_MIRROR_URL,
26
+ LLM_JP_EVAL_DATASETS_URL,
27
+ )
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ _REQUEST_TIMEOUT_SEC = 10
32
+ _MAX_ATTEMPTS = 3
33
+ _BACKOFF_BASE_SEC = 1.0
34
+
35
+
36
+ def _fetch_from_url(url: str) -> str:
37
+ """指定 URL から YAML テキストを取得する。指数バックオフでリトライする。
38
+
39
+ Args:
40
+ url: 取得対象の YAML の URL。
41
+
42
+ Returns:
43
+ 取得した YAML テキスト。
44
+
45
+ Raises:
46
+ requests.RequestException: 全リトライが失敗した場合。
47
+ """
48
+ for attempt in range(1, _MAX_ATTEMPTS + 1):
49
+ try:
50
+ response = requests.get(url, timeout=_REQUEST_TIMEOUT_SEC)
51
+ response.raise_for_status()
52
+ return response.text
53
+ except requests.RequestException as exc:
54
+ logger.warning("upstream YAML の取得に失敗 (%s, 試行 %d/%d): %s", url, attempt, _MAX_ATTEMPTS, exc)
55
+ if attempt >= _MAX_ATTEMPTS:
56
+ raise
57
+ time.sleep(_BACKOFF_BASE_SEC * 2 ** (attempt - 1))
58
+
59
+
60
+ def _fetch_from_network() -> str:
61
+ """upstream raw → CDN ミラーの順にネットワーク取得を試みる。
62
+
63
+ Returns:
64
+ 最初に成功した経路の YAML テキスト。
65
+
66
+ Raises:
67
+ requests.RequestException: 全経路が失敗した場合 (ミラーの例外を送出)。
68
+ """
69
+ try:
70
+ return _fetch_from_url(LLM_JP_EVAL_DATASETS_URL)
71
+ except requests.RequestException:
72
+ logger.warning("raw 取得に失敗。CDN ミラーにフォールバックする: %s", LLM_JP_EVAL_DATASETS_MIRROR_URL)
73
+ return _fetch_from_url(LLM_JP_EVAL_DATASETS_MIRROR_URL)
74
+
75
+
76
+ def _write_cache(text: str) -> None:
77
+ """取得済み YAML テキストを永続キャッシュへ退避する (ベストエフォート)。
78
+
79
+ 書き込み失敗は起動を止める理由にならないため、警告ログに留めて握り潰す。
80
+ """
81
+ try:
82
+ LLM_JP_EVAL_DATASETS_CACHE_PATH.parent.mkdir(parents=True, exist_ok=True)
83
+ LLM_JP_EVAL_DATASETS_CACHE_PATH.write_text(text, encoding="utf-8")
84
+ except OSError as exc:
85
+ logger.warning("upstream YAML のキャッシュ書き込みに失敗: %s", exc)
86
+
87
+
88
+ def _read_local(reason: str) -> str | None:
89
+ """永続キャッシュ → 同梱フォールバックの順にローカルから読む。
90
+
91
+ Args:
92
+ reason: フォールバックに至った理由 (ログ用)。
93
+
94
+ Returns:
95
+ 読み込めた YAML テキスト。どちらも存在しなければ None。
96
+ """
97
+ for label, path in (
98
+ ("永続キャッシュ", LLM_JP_EVAL_DATASETS_CACHE_PATH),
99
+ ("同梱フォールバック", LLM_JP_EVAL_DATASETS_FALLBACK_PATH),
100
+ ):
101
+ try:
102
+ text = path.read_text(encoding="utf-8")
103
+ except OSError:
104
+ continue
105
+ logger.warning("%s のため %s (%s) を使用する", reason, label, path)
106
+ return text
107
+ return None
108
 
109
 
110
  @functools.lru_cache(maxsize=1)
 
112
  """llm-jp-eval upstream から all_datasets.yaml の全内容を取得して返す。
113
 
114
  categories / datasets / dataset_info_overrides を含む dict。
115
+ プロセス起動中は lru_cache で memoize する。ネットワーク取得に成功すれば
116
+ 永続キャッシュを更新し、失敗時はキャッシュ / 同梱フォールバックへ退避する。
117
+
118
+ Returns:
119
+ upstream YAML をパースした dict。
120
+
121
+ Raises:
122
+ requests.RequestException: ネットワーク取得が全滅し、かつローカルの
123
+ キャッシュ・同梱フォールバックも読めなかった場合。
124
  """
125
+ try:
126
+ text = _fetch_from_network()
127
+ _write_cache(text)
128
+ except requests.RequestException as exc:
129
+ text = _read_local(reason=f"upstream/ミラー取得失敗 ({exc})")
130
+ if text is None:
131
+ raise
132
+ return yaml.safe_load(text)
133
 
134
 
135
  def load_canonical_dataset_set() -> frozenset[str]: