ARslan-Ahamd's picture
Deploy from GitHub commit 9c1d82a
465b477 verified
Raw History Blame Contribute Delete
4.22 kB
"""YAML configuration, model registry and run manifests."""
from __future__ import annotations
import json
import platform
import subprocess
from datetime import UTC, datetime
from importlib import metadata
from pathlib import Path
from typing import Any
import yaml
from pydantic import BaseModel, Field
from .bench import BenchConfig
from .chunking import ChunkingConfig
from .llm import CachedClient, ModelSpec, build_client
from .llm.limit import ConcurrencyLimited
from .methods import MethodConfig
from .prompts import PROMPT_VERSION
from .tokens import tokenizer_name
class Paths(BaseModel):
raw: str = "data/raw"
processed: str = "data/processed"
graph: str = "data/graph"
bench: str = "data/bench"
runs: str = "runs"
llm_cache: str = "cache/llm"
class CorpusConfig(BaseModel):
splits: list[str] = ["test", "validation"]
n_papers: int = 120
min_mentions: int = 2
class ExtractionConfig(BaseModel):
model: str = "extractor"
workers: int = 4
nli_confidence: bool = True
nli_model: str = "cross-encoder/nli-deberta-v3-small"
curated_aliases: str = "configs/aliases.yaml"
class ProjectConfig(BaseModel):
paths: Paths = Field(default_factory=Paths)
corpus: CorpusConfig = Field(default_factory=CorpusConfig)
chunking: ChunkingConfig = Field(default_factory=ChunkingConfig)
extraction: ExtractionConfig = Field(default_factory=ExtractionConfig)
bench: BenchConfig = Field(default_factory=BenchConfig)
paraphrase_model: str | None = None
models_file: str = "configs/models.yaml"
class ExperimentConfig(BaseModel):
name: str
bench_file: str
allow_unreviewed: bool = False # only for pipeline smoke tests; never for reported results
limit: int | None = None
base_model: str
judge_model: str | None = None
nli_verifier: bool = True
nli_model: str = "cross-encoder/nli-deberta-v3-small"
min_support: float | None = 0.5
workers: int = 4
methods: list[MethodConfig]
reference_method: str = "text_rag"
def load_yaml(path: str | Path) -> dict[str, Any]:
return yaml.safe_load(Path(path).read_text(encoding="utf-8")) or {}
def load_project(path: str | Path = "configs/default.yaml") -> ProjectConfig:
p = Path(path)
return ProjectConfig(**load_yaml(p)) if p.exists() else ProjectConfig()
def load_models(path: str | Path) -> dict[str, ModelSpec]:
return {name: ModelSpec(**spec) for name, spec in load_yaml(path).get("models", {}).items()}
def make_client(name: str, project: ProjectConfig, offline: bool = False) -> CachedClient:
models = load_models(project.models_file)
if name not in models:
raise KeyError(f"model '{name}' is not defined in {project.models_file}; known: {sorted(models)}")
spec = models[name]
# Cache outside the limiter: hits never wait; misses respect the model's max_concurrency.
return CachedClient(ConcurrencyLimited(build_client(spec), spec.max_concurrency), project.paths.llm_cache, offline=offline)
def _git_commit() -> str | None:
try:
return subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True, text=True, check=True, timeout=10).stdout.strip()
except Exception: # noqa: BLE001 - git may be missing or the tree may not be a repo; the commit is optional metadata
return None
def manifest(extra: dict[str, Any]) -> dict[str, Any]:
pkgs = {}
for name in ("graphtrace", "networkx", "rank-bm25", "rapidfuzz", "tiktoken", "pydantic", "anthropic", "httpx", "sentence-transformers", "torch"):
try:
pkgs[name] = metadata.version(name)
except metadata.PackageNotFoundError:
pass
return {
"created_utc": datetime.now(UTC).isoformat(),
"prompt_version": PROMPT_VERSION,
"tokenizer": tokenizer_name(),
"git_commit": _git_commit(),
"python": platform.python_version(),
"platform": platform.platform(),
"packages": pkgs,
**extra,
}
def write_json(path: str | Path, obj: Any) -> None:
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(json.dumps(obj, indent=2, ensure_ascii=False, default=str), encoding="utf-8")