Spaces:
Running
Running
File size: 5,039 Bytes
9c84f9d dd41633 9c84f9d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | """์ค์ ์ **๋ด์ฉ ์ง๋ฌธ** โ ์ด๋ฆ์ด ์๋๋ผ ๋ด์ฉ์ผ๋ก ์คํ์ ์๋ณํ๋ค.
์ ํ์ํ๊ฐ: ์ด ์์ง์ ์์ฐ์ ์ค๋ ์ด๋ฆ์ผ๋ก๋ง ์๋ณ๋ผ ์๊ณ (`prompt_dev_v23`
โ `pipeline_dev_v24` ์ฒ๋ผ ์ง์ ๋ฒํธ๊ฐ ์ด๊ธ๋๋ ์ผ์ด ์ฆ์๋ค), ๊ทธ๋์
"์ด๋ฆ์ด ๋ค๋ฅธ ๋ run ์ด ์ฌ์ค ๊ฐ์ ์ค์ " ์ธ์ง๋ฅผ ์ฌ๋์ด ํด์๋ฅผ ๋ ์ ๋์กฐํด์ผ ํ๋ค
(`insights/lab_notes_archive.md#260803_etd_regression_prod_vs_v24` ์ '์ด๋ฆ ๋งคํ' ์ ์ด ๊ทธ ํ์ ์ด๋ค).
run ์ ์ง๋ฌธ์ ์ฐ์ด ๋๋ฉด ๊ทธ ๋์กฐ๊ฐ ์กฐํ ํ ๋ฒ์ผ๋ก ๋๋๋ค.
์ง๋ฌธ์ **ํด์์ด ๋๋ ์ค์ **์ ๋์์ผ๋ก ๊ณ์ฐํ๋ค. `extends`/override ๋ก ์กฐ๋ฆฝํ
ํ์ดํ๋ผ์ธ๊ณผ ๊ฐ์ ๋ด์ฉ์ ํต์งธ๋ก ์ ์ด ๋ ํ์ผ์ ๊ฐ์ ์ง๋ฌธ์ ๊ฐ์ ธ์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค
(๊ทธ ๋ฑ๊ฐ์ฑ์ด ํ์ผ ๋ณต์ ๋ฅผ override ๋ก ์ฎ๊ธธ ๋์ ์์ ๋ง์ด๋ค).
ํ์: `sha256:<์ 12์>` โ ๋์ผ๋ก ๋น๊ตํ ์ ์์ ๋งํผ ์งง๊ณ , ํ๋ก์ ํธ๋น ์๋ฐฑ ๊ฐ
๊ท๋ชจ์์ ์ถฉ๋ํ์ง ์์ ๋งํผ ๊ธธ๋ค.
"""
import hashlib
import json
from collections.abc import Mapping, Sequence
from pathlib import Path
from typing import Any
#: ์ง๋ฌธ ๊ณ์ฐ์์ ์ ์ธํ๋ ํค โ ์ฌ๋์ด ์ฝ๋ ๋ผ๋ฒจ์ผ ๋ฟ ์คํ์ ์ํฅ์ ์ฃผ์ง ์๋๋ค.
#: ์ด๊ฑธ ์ ์ธํด์ผ "์ด๋ฆ๋ง ๋ฐ๊พผ ์ฌ๋ณธ"์ด ๊ฐ์ ์ง๋ฌธ์ผ๋ก ์กํ๋ค.
NON_FUNCTIONAL_KEYS = frozenset({"name", "description", "comment", "extends", "overrides"})
#: ์คํ
ํ์์์ ์ ์ธํ๋ ํค โ ์คํ
์ `name` ์ ํ๋กฌํํธ ํคยทrule ํ์
์กฐํ์
#: ์ฐ์ด๋ฏ๋ก (`pipelines/registry.py:resolve_step`) ๊ธฐ๋ฅ์ ์ด๋ค. ์ง์ฐ๋ฉด ์ ๋๋ค.
NON_FUNCTIONAL_STEP_KEYS = frozenset({"description", "comment"})
#: ํ๋กฌํํธ ๋๋ ํ ๋ฆฌ์์ ์ง๋ฌธ ๋์์ผ๋ก ์ผ๋ ํ์ฅ์.
PROMPT_ASSET_SUFFIXES = frozenset({".txt", ".yaml", ".yml", ".json"})
_DIGEST_PREFIX = "sha256"
_DIGEST_LENGTH = 12
def digest(payload: Any) -> str:
"""JSON ์ง๋ ฌํ ๊ฐ๋ฅํ ๊ฐ์ ์ ๊ท ์ง๋ฌธ."""
canonical = json.dumps(payload, sort_keys=True, ensure_ascii=False, separators=(",", ":"))
full = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
return f"{_DIGEST_PREFIX}:{full[:_DIGEST_LENGTH]}"
def pipeline_fingerprint(config: Mapping[str, Any] | None) -> str | None:
"""ํด์์ด ๋๋ ํ์ดํ๋ผ์ธ ์ค์ ์ ์ง๋ฌธ.
Args:
config: `extends`/override ํด์๊น์ง ๋๋ ํ์ดํ๋ผ์ธ dict.
None ์ด๊ฑฐ๋ ๋น์ด ์์ผ๋ฉด None ์ ๋ฐํํ๋ค (single_step ์ฒ๋ผ ํ์ดํ๋ผ์ธ
ํ์ผ์ด ์๋ ํ์คํฌ๊ฐ ์๋ค).
Returns:
`sha256:xxxxxxxxxxxx` ๋๋ None.
"""
if not config:
return None
return digest(_strip_pipeline(config))
def prompt_fingerprint(path: Path) -> str | None:
"""ํ๋กฌํํธ ์์ฐ์ ์ง๋ฌธ โ ๋๋ ํ ๋ฆฌ(์ฌ๋ฌ ํ์ผ)์ ๋จ์ผ ํ์ผ ๋ชจ๋ ์ง์ํ๋ค.
๋๋ ํ ๋ฆฌ๋ฉด ํ์ ์์ฐ ํ์ผ์ ์๋๊ฒฝ๋ก๋ก ์ ๋ ฌํด `{๊ฒฝ๋ก: ๋ด์ฉํด์}` ๋งต์ ๋ง๋ค๊ณ
๊ทธ ๋งต์ ์ง๋ฌธ์ ๋ธ๋ค. ํ์ผ ํ๋๊ฐ ๋ฐ๋๋ฉด ์ ์ฒด ์ง๋ฌธ์ด ๋ฐ๋๊ณ , ํ์ผ ์์๋
๋๋ ํ ๋ฆฌ ์ด๋ฆ์ด ๋ฐ๋์ด๋ ๋ด์ฉ์ด ๊ฐ์ผ๋ฉด ์ง๋ฌธ์ ์ ์ง๋๋ค.
Args:
path: ํ๋กฌํํธ ๋๋ ํ ๋ฆฌ ๋๋ YAML ํ์ผ ๊ฒฝ๋ก.
Returns:
`sha256:xxxxxxxxxxxx`, ๊ฒฝ๋ก๊ฐ ์๊ฑฐ๋ ์ฝ์ ์ ์์ผ๋ฉด None.
"""
try:
if path.is_dir():
assets = _collect_prompt_assets(path)
if not assets:
return None
return digest(assets)
if path.is_file():
return digest({path.name: _file_digest(path)})
except OSError:
return None
return None
def _collect_prompt_assets(root: Path) -> dict[str, str]:
"""ํ๋กฌํํธ ๋๋ ํ ๋ฆฌ์ `{์๋๊ฒฝ๋ก: ํ์ผ์ง๋ฌธ}` ๋งต. ์จ๊น ํ์ผ์ ์ ์ธํ๋ค."""
assets: dict[str, str] = {}
for file in sorted(root.rglob("*")):
if not file.is_file() or file.suffix.lower() not in PROMPT_ASSET_SUFFIXES:
continue
if any(part.startswith(".") for part in file.relative_to(root).parts):
continue
assets[file.relative_to(root).as_posix()] = _file_digest(file)
return assets
def _file_digest(path: Path) -> str:
"""ํ์ผ ๋ฐ์ดํธ์ ์ง๋ฌธ โ ํ
์คํธ ์ธ์ฝ๋ฉยท๊ฐํ ์ ๊ทํ๋ฅผ ํ์ง ์๋๋ค(์๋ ๊ทธ๋๋ก)."""
return hashlib.sha256(path.read_bytes()).hexdigest()[:_DIGEST_LENGTH]
def _strip_pipeline(config: Mapping[str, Any]) -> dict[str, Any]:
"""ํ์ดํ๋ผ์ธ dict ์์ ๋น๊ธฐ๋ฅ ํ๋๋ฅผ ๊ฑท์ด๋ธ ์ฌ๋ณธ."""
stripped = {k: v for k, v in config.items() if k not in NON_FUNCTIONAL_KEYS}
steps = stripped.get("steps")
if isinstance(steps, Sequence) and not isinstance(steps, str | bytes):
stripped["steps"] = [_strip_step(s) for s in steps]
return stripped
def _strip_step(step: Any) -> Any:
if not isinstance(step, Mapping):
return step
return {k: v for k, v in step.items() if k not in NON_FUNCTIONAL_STEP_KEYS}
|