dentate 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dentate/__init__.py +10 -0
- dentate/_deps.py +38 -0
- dentate/adapters/__init__.py +7 -0
- dentate/adapters/oracle/__init__.py +5 -0
- dentate/adapters/oracle/fake.py +62 -0
- dentate/adapters/teacher/__init__.py +5 -0
- dentate/adapters/teacher/fake.py +27 -0
- dentate/adapters/tracking/__init__.py +6 -0
- dentate/adapters/tracking/null.py +19 -0
- dentate/adapters/tracking/wandb.py +53 -0
- dentate/agent.py +76 -0
- dentate/analysis.py +421 -0
- dentate/analysis_llm.py +247 -0
- dentate/api.py +1255 -0
- dentate/arch/__init__.py +115 -0
- dentate/arch/_baseline_torch.py +110 -0
- dentate/arch/_xlstm_torch.py +122 -0
- dentate/arch/baseline.py +84 -0
- dentate/arch/knobs.py +159 -0
- dentate/arch/registry.py +72 -0
- dentate/arch/xlstm.py +86 -0
- dentate/backends/__init__.py +11 -0
- dentate/backends/_stub.py +55 -0
- dentate/backends/cuda.py +16 -0
- dentate/backends/factory.py +31 -0
- dentate/backends/fake.py +165 -0
- dentate/backends/hf.py +317 -0
- dentate/backends/mlx.py +171 -0
- dentate/bootcamp/__init__.py +8 -0
- dentate/bootcamp/__main__.py +89 -0
- dentate/bootcamp/app.py +438 -0
- dentate/bootcamp/auth.py +231 -0
- dentate/bootcamp/config.py +121 -0
- dentate/bootcamp/experiment.py +277 -0
- dentate/bootcamp/jobs.py +434 -0
- dentate/bootcamp/papers.py +60 -0
- dentate/bootcamp/project.py +111 -0
- dentate/bootcamp/repository.py +240 -0
- dentate/bootcamp/resources.py +97 -0
- dentate/bootcamp/worker.py +247 -0
- dentate/cli.py +1885 -0
- dentate/compute.py +141 -0
- dentate/config.py +166 -0
- dentate/control.py +63 -0
- dentate/data/__init__.py +25 -0
- dentate/data/dataset.py +44 -0
- dentate/data/replay.py +53 -0
- dentate/data/schemas.py +63 -0
- dentate/demo/NOTICE +20 -0
- dentate/demo/TOKENIZER.json +12 -0
- dentate/demo/__init__.py +159 -0
- dentate/demo/starter.dentate +10 -0
- dentate/demo/tokenizer/merges.txt +48901 -0
- dentate/demo/tokenizer/special_tokens_map.json +28 -0
- dentate/demo/tokenizer/tokenizer.json +98249 -0
- dentate/demo/tokenizer/tokenizer_config.json +154 -0
- dentate/demo/tokenizer/vocab.json +1 -0
- dentate/distill/__init__.py +6 -0
- dentate/distill/judge_distill.py +24 -0
- dentate/domain/__init__.py +57 -0
- dentate/domain/ports.py +128 -0
- dentate/domain/types.py +145 -0
- dentate/errors.py +57 -0
- dentate/eval/__init__.py +20 -0
- dentate/eval/baselines.py +75 -0
- dentate/eval/evaluate.py +63 -0
- dentate/eval/report.py +25 -0
- dentate/eval/stats.py +48 -0
- dentate/export_smi.py +196 -0
- dentate/harness.py +284 -0
- dentate/ingest.py +21 -0
- dentate/integrations/__init__.py +37 -0
- dentate/integrations/compute.py +336 -0
- dentate/integrations/huggingface.py +233 -0
- dentate/integrations/secrets.py +112 -0
- dentate/integrations/wandb_link.py +29 -0
- dentate/manifest.py +81 -0
- dentate/model_docs.py +152 -0
- dentate/models/__init__.py +22 -0
- dentate/models/registry.py +141 -0
- dentate/models/userstore.py +53 -0
- dentate/obs.py +190 -0
- dentate/online/__init__.py +5 -0
- dentate/online/loop.py +78 -0
- dentate/os_sim/__init__.py +17 -0
- dentate/os_sim/cache.py +54 -0
- dentate/os_sim/checker.py +71 -0
- dentate/os_sim/dataset.py +180 -0
- dentate/os_sim/engine.py +462 -0
- dentate/os_sim/enrich.py +99 -0
- dentate/os_sim/filesystem.py +160 -0
- dentate/os_sim/grounding.py +64 -0
- dentate/os_sim/import_traces.py +341 -0
- dentate/os_sim/manage.py +93 -0
- dentate/os_sim/memory.py +150 -0
- dentate/os_sim/persona.py +51 -0
- dentate/os_sim/reward_bridge.py +123 -0
- dentate/os_sim/risk.py +50 -0
- dentate/os_sim/scenarios.py +172 -0
- dentate/os_sim/session.py +221 -0
- dentate/os_sim/shell_llm.py +127 -0
- dentate/os_sim/state.py +88 -0
- dentate/os_sim/templates.py +130 -0
- dentate/os_sim/trajectory.py +77 -0
- dentate/os_sim/vfs.py +260 -0
- dentate/package.py +113 -0
- dentate/playground.py +934 -0
- dentate/presets.py +96 -0
- dentate/pretrain/__init__.py +15 -0
- dentate/pretrain/agentic_data.py +153 -0
- dentate/pretrain/agentic_eval.py +102 -0
- dentate/pretrain/agentic_run.py +300 -0
- dentate/pretrain/assoc.py +261 -0
- dentate/pretrain/behaviors.py +83 -0
- dentate/pretrain/benchmark.py +102 -0
- dentate/pretrain/best_ckpt.py +60 -0
- dentate/pretrain/capacity.py +145 -0
- dentate/pretrain/certificate.py +116 -0
- dentate/pretrain/chatml.py +144 -0
- dentate/pretrain/code_env.py +310 -0
- dentate/pretrain/coherence.py +62 -0
- dentate/pretrain/corpusstats.py +166 -0
- dentate/pretrain/curator.py +177 -0
- dentate/pretrain/deeploop.py +104 -0
- dentate/pretrain/derivation.py +264 -0
- dentate/pretrain/gates.py +111 -0
- dentate/pretrain/grpo.py +308 -0
- dentate/pretrain/grpo_run.py +217 -0
- dentate/pretrain/hf_reason_data.py +169 -0
- dentate/pretrain/llc.py +98 -0
- dentate/pretrain/lm_replay.py +47 -0
- dentate/pretrain/longctx.py +61 -0
- dentate/pretrain/longctx_data.py +80 -0
- dentate/pretrain/longctx_train.py +66 -0
- dentate/pretrain/managed.py +493 -0
- dentate/pretrain/meaningful_eval.py +164 -0
- dentate/pretrain/metrics.py +391 -0
- dentate/pretrain/ntgrid.py +95 -0
- dentate/pretrain/oracle_traces.py +274 -0
- dentate/pretrain/passk.py +110 -0
- dentate/pretrain/pool.py +139 -0
- dentate/pretrain/presets.py +84 -0
- dentate/pretrain/preview.py +101 -0
- dentate/pretrain/probes.py +147 -0
- dentate/pretrain/reason_data.py +150 -0
- dentate/pretrain/reason_run.py +454 -0
- dentate/pretrain/reason_trainer.py +276 -0
- dentate/pretrain/recall_data.py +153 -0
- dentate/pretrain/recall_run.py +292 -0
- dentate/pretrain/smoltalk.py +44 -0
- dentate/pretrain/teacher.py +229 -0
- dentate/pretrain/tools.py +103 -0
- dentate/pretrain/trainer.py +764 -0
- dentate/pretrain/vision.py +181 -0
- dentate/pretrain/vl_run.py +110 -0
- dentate/pretrain/wmprobe.py +235 -0
- dentate/pretrain/world_model.py +150 -0
- dentate/primitives/__init__.py +19 -0
- dentate/primitives/losses.py +156 -0
- dentate/primitives/optimizer.py +28 -0
- dentate/primitives/sampling.py +55 -0
- dentate/primitives/schedules.py +20 -0
- dentate/reward/__init__.py +11 -0
- dentate/reward/combine.py +64 -0
- dentate/reward/reward_model.py +47 -0
- dentate/reward/service.py +57 -0
- dentate/runconfig.py +119 -0
- dentate/runkind/__init__.py +18 -0
- dentate/runkind/base.py +55 -0
- dentate/runkind/capabilities.py +46 -0
- dentate/runkind/distill.py +52 -0
- dentate/runkind/reason.py +63 -0
- dentate/runkind/rlvr.py +61 -0
- dentate/sandbox.py +146 -0
- dentate/scripts/__init__.py +1 -0
- dentate/scripts/campaign_100m.py +173 -0
- dentate/serve_openai.py +281 -0
- dentate/site.py +290 -0
- dentate/store.py +420 -0
- dentate/tasks/__init__.py +11 -0
- dentate/tasks/hf.py +151 -0
- dentate/tasks/masking.py +161 -0
- dentate/tasks/multi.py +59 -0
- dentate/tasks/reason_source.py +59 -0
- dentate/tasks/synthetic.py +89 -0
- dentate/train/__init__.py +6 -0
- dentate/train/curriculum.py +277 -0
- dentate/train/pipeline.py +383 -0
- dentate/train/stages.py +65 -0
- dentate/ui.py +189 -0
- dentate/util.py +39 -0
- dentate/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- dentate/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- dentate/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- dentate/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- dentate/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- dentate/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- dentate/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- dentate/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- dentate/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- dentate/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- dentate/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- dentate/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- dentate/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- dentate/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- dentate/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- dentate/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- dentate/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- dentate/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- dentate/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- dentate/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- dentate/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- dentate/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- dentate/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- dentate/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- dentate/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- dentate/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- dentate/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- dentate/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- dentate/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- dentate/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- dentate/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- dentate/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- dentate/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- dentate/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- dentate/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- dentate/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- dentate/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- dentate/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- dentate/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- dentate/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- dentate/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- dentate/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- dentate/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- dentate/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- dentate/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- dentate/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- dentate/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- dentate/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- dentate/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- dentate/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- dentate/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- dentate/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- dentate/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- dentate/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- dentate/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- dentate/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- dentate/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- dentate/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- dentate/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- dentate/web/assets/index-DsBOi9cQ.js +1058 -0
- dentate/web/assets/index-fQgsX_rq.css +41 -0
- dentate/web/favicon.svg +19 -0
- dentate/web/icon-192.png +0 -0
- dentate/web/icon-512.png +0 -0
- dentate/web/index.html +18 -0
- dentate/web/manifest.webmanifest +15 -0
- dentate/web/media/dentate.jpg +0 -0
- dentate/web/media/dentate.mp4 +0 -0
- dentate/web/sw.js +46 -0
- dentate-0.1.0.dist-info/METADATA +423 -0
- dentate-0.1.0.dist-info/RECORD +267 -0
- dentate-0.1.0.dist-info/WHEEL +5 -0
- dentate-0.1.0.dist-info/entry_points.txt +2 -0
- dentate-0.1.0.dist-info/licenses/LICENSE +202 -0
- dentate-0.1.0.dist-info/licenses/dentate/demo/NOTICE +20 -0
- dentate-0.1.0.dist-info/top_level.txt +1 -0
dentate/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Cortex Dentate — RLVR + distillation training over Gauntlet verifiable rewards.
|
|
2
|
+
|
|
3
|
+
P0 scaffold: typed config + model registry + CLI surface. Training loops (SFT/GKD/DPO/GRPO/online) land in
|
|
4
|
+
P1+ (see PLAN.md and docs/). Three tiers (docs/ARCHITECTURE.md): a 1–5B trainable *student* (default
|
|
5
|
+
Qwen3-4B-2507), a local *teacher* (default Gemma 4 ⇄ Qwen3-MoE) for bulk sampling + on-policy distillation,
|
|
6
|
+
and an API *oracle* that authors/judges. Reward = R1 verifiable (the reused Gauntlet composite — one source
|
|
7
|
+
of truth, no metric drift) ⊕ R2 reference (masking) ⊕ R3 AI-feedback (oracle RLAIF/PRM, gated inside R1).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "0.0.0"
|
dentate/_deps.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Thread-safe access to the heavy third-party deps dentate imports lazily.
|
|
2
|
+
|
|
3
|
+
`transformers` builds its public namespace lazily: its package `__init__` runs a few hundred lines and
|
|
4
|
+
only as its LAST statement swaps `sys.modules["transformers"]` for the `_LazyModule` that resolves
|
|
5
|
+
`AutoTokenizer` & friends. A second thread running `from transformers import AutoTokenizer` inside that
|
|
6
|
+
window finds the half-built module already in `sys.modules`, misses the attribute, and dies with
|
|
7
|
+
|
|
8
|
+
ImportError: cannot import name 'AutoTokenizer' from 'transformers'
|
|
9
|
+
|
|
10
|
+
Dentate hits that window routinely, because it imports transformers off the main thread from more than
|
|
11
|
+
one place at once: the dashboard resolves examples in FastAPI's threadpool (the Analysis tab requests the
|
|
12
|
+
`train` and `eval` splits concurrently) and `RunManager` trains in background threads. Measured on
|
|
13
|
+
transformers 4.57.3, 8 threads racing a cold `from transformers import AutoTokenizer` fail 5 times.
|
|
14
|
+
|
|
15
|
+
So every dentate-side transformers import goes through `hf()`, which serializes the package build. The
|
|
16
|
+
lock is only ever contended on the first call; afterwards it is an uncontended acquire around attribute
|
|
17
|
+
lookups on an already-built module.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import threading
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
_LOCK = threading.Lock()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def hf(*names: str) -> tuple[Any, ...]:
|
|
29
|
+
"""`from transformers import <names>`, serialized against the lazy-init race described above.
|
|
30
|
+
|
|
31
|
+
Import-site replacement, so it unpacks exactly like the statement it replaces::
|
|
32
|
+
|
|
33
|
+
(AutoTokenizer,) = hf("AutoTokenizer")
|
|
34
|
+
AutoModelForCausalLM, AutoTokenizer = hf("AutoModelForCausalLM", "AutoTokenizer")
|
|
35
|
+
"""
|
|
36
|
+
with _LOCK:
|
|
37
|
+
import transformers # noqa: PLC0415 — heavy dep, deliberately deferred to this single site
|
|
38
|
+
return tuple(getattr(transformers, name) for name in names)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Concrete non-backend adapters: fake oracle/teacher plus experiment tracking."""
|
|
2
|
+
|
|
3
|
+
from .oracle import FakeOracle
|
|
4
|
+
from .teacher import FakeTeacher
|
|
5
|
+
from .tracking import NullTracker, TrackerError, WandbTracker, make_tracker
|
|
6
|
+
|
|
7
|
+
__all__ = ["FakeOracle", "FakeTeacher", "NullTracker", "TrackerError", "WandbTracker", "make_tracker"]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""FakeOracle — a deterministic stand-in for the API oracle (author / solve / judge / decompose).
|
|
2
|
+
|
|
3
|
+
It is a *strong* judge: it knows the task target (the tasks were generated here), so its R3 scores and rankings
|
|
4
|
+
are meaningful learning signal — exactly the RLAIF setup, minus the API cost. Optional `noise` simulates an
|
|
5
|
+
imperfect judge so the reward-model-distillation drift checks have something to detect.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import random
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
from ...domain.types import Rollout, Task, Verdict
|
|
14
|
+
from ...util import parse_wrapped, similarity, stable_seed
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FakeOracle:
|
|
18
|
+
"""Implements the `Oracle` port."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, noise: float = 0.0, seed: int = 0) -> None:
|
|
21
|
+
self.noise = noise
|
|
22
|
+
self.seed = seed
|
|
23
|
+
|
|
24
|
+
def author(self, seed: int, domain: str) -> Task:
|
|
25
|
+
# Lazy import to avoid an adapters→tasks import at module load.
|
|
26
|
+
if domain == "synthetic":
|
|
27
|
+
from ...tasks.synthetic import SyntheticTasks
|
|
28
|
+
|
|
29
|
+
return SyntheticTasks().sample(1, seed)[0]
|
|
30
|
+
from ...tasks.masking import MaskingTasks
|
|
31
|
+
|
|
32
|
+
return MaskingTasks(domain=domain).sample(1, seed)[0]
|
|
33
|
+
|
|
34
|
+
def solve(self, task: Task) -> Rollout:
|
|
35
|
+
"""An expert trace (for SeqKD/RAFT warm-start) — the oracle solves it correctly."""
|
|
36
|
+
completion = f"<out>{task.target}</out>" if task.target is not None else ""
|
|
37
|
+
return Rollout(task_id=task.id, prompt=task.prompt, completion=completion, policy_version=-1,
|
|
38
|
+
meta={"role": "oracle"})
|
|
39
|
+
|
|
40
|
+
def judge(self, task: Task, rollouts: Sequence[Rollout]) -> list[Verdict]:
|
|
41
|
+
scores = [self._quality(task, r, i) for i, r in enumerate(rollouts)]
|
|
42
|
+
order = sorted(range(len(rollouts)), key=lambda i: scores[i], reverse=True)
|
|
43
|
+
rank_of = {i: rank for rank, i in enumerate(order)}
|
|
44
|
+
return [Verdict(score=round(scores[i], 4), rank=rank_of[i]) for i in range(len(rollouts))]
|
|
45
|
+
|
|
46
|
+
def decompose(self, task: Task) -> list[str]:
|
|
47
|
+
if task.steps:
|
|
48
|
+
return list(task.steps)
|
|
49
|
+
if task.target:
|
|
50
|
+
return [s for s in task.target.split("\n") if s] or [task.target]
|
|
51
|
+
return [task.prompt[:24]]
|
|
52
|
+
|
|
53
|
+
def _quality(self, task: Task, rollout: Rollout, idx: int) -> float:
|
|
54
|
+
inner = parse_wrapped(rollout.completion, "out")
|
|
55
|
+
if inner is None:
|
|
56
|
+
return 0.0
|
|
57
|
+
r1 = 1.0 if inner == task.target else 0.0
|
|
58
|
+
quality = 0.5 * r1 + 0.5 * similarity(inner, task.target or "")
|
|
59
|
+
if self.noise:
|
|
60
|
+
rng = random.Random(stable_seed("judge", self.seed, task.id, idx))
|
|
61
|
+
quality += (rng.random() * 2 - 1) * self.noise
|
|
62
|
+
return min(1.0, max(0.0, quality))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""FakeTeacher — a competent *local* model: bulk-samples candidates and exposes logprobs (enabling GKD).
|
|
2
|
+
|
|
3
|
+
Implemented by reusing `FakeBackend` at a fixed high skill, so the teacher is just a strong, logprob-exposing
|
|
4
|
+
policy — which is exactly what a local teacher is. The API oracle, by contrast, has no logprobs (FakeOracle).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
from ...backends.fake import FakeBackend, Solver
|
|
12
|
+
from ...domain.types import Policy, Rollout, Task
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FakeTeacher:
|
|
16
|
+
"""Implements the `Teacher` port."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, solver: Solver, model_key: str = "gemma-4", skill: float = 0.85, group_size: int = 4) -> None:
|
|
19
|
+
self.model_key = model_key
|
|
20
|
+
self._backend = FakeBackend(solver, group_size=group_size)
|
|
21
|
+
self._policy = Policy(model_key=model_key, version=0, params={"skill": skill})
|
|
22
|
+
|
|
23
|
+
def sample(self, tasks: Sequence[Task], n: int = 1, seed: int = 0) -> list[list[Rollout]]:
|
|
24
|
+
return self._backend.generate(self._policy, [t.prompt for t in tasks], n=n, seed=seed)
|
|
25
|
+
|
|
26
|
+
def logprobs(self, prompt: str, completion: str) -> list[float]:
|
|
27
|
+
return self._backend.logprobs(self._policy, prompt, completion)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Experiment trackers — `NullTracker` (tests) and a fail-loud W&B tracker via `make_tracker`."""
|
|
2
|
+
|
|
3
|
+
from .null import NullTracker
|
|
4
|
+
from .wandb import TrackerError, WandbTracker, make_tracker
|
|
5
|
+
|
|
6
|
+
__all__ = ["NullTracker", "TrackerError", "WandbTracker", "make_tracker"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""A no-op tracker — the default in tests and when W&B is disabled/absent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NullTracker:
|
|
7
|
+
"""Implements the `Tracker` port; drops everything. Usable as a context manager."""
|
|
8
|
+
|
|
9
|
+
def log(self, metrics: dict[str, float], step: int | None = None) -> None:
|
|
10
|
+
return None
|
|
11
|
+
|
|
12
|
+
def summary(self, data: dict[str, float]) -> None:
|
|
13
|
+
return None
|
|
14
|
+
|
|
15
|
+
def __enter__(self) -> NullTracker:
|
|
16
|
+
return self
|
|
17
|
+
|
|
18
|
+
def __exit__(self, *exc: object) -> bool:
|
|
19
|
+
return False
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Weights & Biases tracker. Disabled tracking uses `NullTracker`; requested W&B failures are explicit."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ...config import WandbSpec
|
|
6
|
+
from ...domain.ports import Tracker
|
|
7
|
+
from ...errors import TrackerError
|
|
8
|
+
from .null import NullTracker
|
|
9
|
+
|
|
10
|
+
__all__ = ["TrackerError", "WandbTracker", "make_tracker"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WandbTracker:
|
|
14
|
+
"""Implements the `Tracker` port over a live W&B run. `wandb` is imported lazily (only on use)."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, spec: WandbSpec) -> None:
|
|
17
|
+
try:
|
|
18
|
+
import wandb # noqa: PLC0415 (lazy by design — keep wandb out of the import graph)
|
|
19
|
+
except ImportError as exc:
|
|
20
|
+
raise TrackerError("W&B tracking was requested, but `wandb` is not importable") from exc
|
|
21
|
+
|
|
22
|
+
self._run = wandb.init(
|
|
23
|
+
project=spec.project, entity=spec.entity, group=spec.run_group or None,
|
|
24
|
+
tags=list(spec.tags) or None,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def url(self) -> str | None:
|
|
29
|
+
"""The live W&B run URL (stored in the manifest's links for cross-dashboard navigation)."""
|
|
30
|
+
return getattr(self._run, "url", None)
|
|
31
|
+
|
|
32
|
+
def log(self, metrics: dict[str, float], step: int | None = None) -> None:
|
|
33
|
+
self._run.log(metrics, step=step)
|
|
34
|
+
|
|
35
|
+
def summary(self, data: dict[str, float]) -> None:
|
|
36
|
+
for key, value in data.items():
|
|
37
|
+
self._run.summary[key] = value
|
|
38
|
+
|
|
39
|
+
def __enter__(self) -> WandbTracker:
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
def __exit__(self, *exc: object) -> bool:
|
|
43
|
+
self._run.finish()
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def make_tracker(spec: WandbSpec) -> Tracker:
|
|
48
|
+
"""Return a live W&B tracker, or a `NullTracker` when tracking is disabled."""
|
|
49
|
+
if not spec.enabled:
|
|
50
|
+
return NullTracker()
|
|
51
|
+
if spec.report_to != "wandb":
|
|
52
|
+
raise TrackerError(f"unsupported tracker {spec.report_to!r}; known: wandb")
|
|
53
|
+
return WandbTracker(spec)
|
dentate/agent.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Agentic tool-calling loop — lets a capable checkpoint use the sandbox tools, then answer.
|
|
2
|
+
|
|
3
|
+
Protocol (model-agnostic text form, works without native tool-calling support): to call a tool the model emits
|
|
4
|
+
<tool_call>{"name": "python", "arguments": {"code": "..."}}</tool_call>
|
|
5
|
+
and stops; it receives a `<tool_result>…</tool_result>` turn and continues; a turn with no tool_call is the final
|
|
6
|
+
answer. `run_agent` drives this against a `generate(messages) -> str` callable and a `Sandbox`, yielding events
|
|
7
|
+
the API streams to the UI (assistant text, tool_call, tool_result + workspace files, done / step-limit).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
from collections.abc import Callable, Iterator
|
|
15
|
+
|
|
16
|
+
from .sandbox import TOOLS, Sandbox, dispatch
|
|
17
|
+
|
|
18
|
+
_CALL_RE = re.compile(r"<tool_call>\s*(\{.*?\})\s*</tool_call>", re.DOTALL)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def system_prompt() -> str:
|
|
22
|
+
lines = [
|
|
23
|
+
"You are an agent that can use tools running in a sandboxed workspace. To call a tool, output EXACTLY:",
|
|
24
|
+
'<tool_call>{"name": "<tool>", "arguments": { ... }}</tool_call>',
|
|
25
|
+
"then stop and wait. You will receive <tool_result>…</tool_result>; then continue. When you have the",
|
|
26
|
+
"answer, reply normally WITHOUT a tool_call. Prefer writing files for anything the user should keep.",
|
|
27
|
+
"",
|
|
28
|
+
"Available tools:",
|
|
29
|
+
]
|
|
30
|
+
lines += [f"- {t['name']}({', '.join(t['arguments'])}): {t['description']}" for t in TOOLS]
|
|
31
|
+
return "\n".join(lines)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def strip_tool_calls(text: str) -> str:
|
|
35
|
+
"""Remove the raw <tool_call> markers from a turn for display (the UI shows a tool card instead)."""
|
|
36
|
+
return _CALL_RE.sub("", text or "").strip()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def parse_tool_call(text: str) -> dict | None:
|
|
40
|
+
"""Extract the first well-formed <tool_call> object, or None (a normal final answer)."""
|
|
41
|
+
m = _CALL_RE.search(text or "")
|
|
42
|
+
if not m:
|
|
43
|
+
return None
|
|
44
|
+
try:
|
|
45
|
+
obj = json.loads(m.group(1))
|
|
46
|
+
except json.JSONDecodeError:
|
|
47
|
+
return None
|
|
48
|
+
if isinstance(obj, dict) and isinstance(obj.get("name"), str):
|
|
49
|
+
args = obj.get("arguments")
|
|
50
|
+
return {"name": obj["name"], "arguments": args if isinstance(args, dict) else {}}
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run_agent(generate: Callable[[list[dict]], str], sandbox: Sandbox, messages: list[dict],
|
|
55
|
+
*, max_steps: int = 6) -> Iterator[dict]:
|
|
56
|
+
"""Drive the tool-calling loop. Yields event dicts; terminates on a tool-free turn or `max_steps`."""
|
|
57
|
+
base_system = next((m.get("content", "") for m in messages if m.get("role") == "system"), "")
|
|
58
|
+
sys_msg = {"role": "system", "content": (base_system + "\n\n" + system_prompt()).strip()}
|
|
59
|
+
convo: list[dict] = [sys_msg, *[m for m in messages if m.get("role") != "system"]]
|
|
60
|
+
|
|
61
|
+
for step in range(max_steps):
|
|
62
|
+
text = generate(convo)
|
|
63
|
+
call = parse_tool_call(text)
|
|
64
|
+
yield {"type": "assistant", "text": text, "step": step}
|
|
65
|
+
convo.append({"role": "assistant", "content": text})
|
|
66
|
+
if not call:
|
|
67
|
+
yield {"type": "done"}
|
|
68
|
+
return
|
|
69
|
+
yield {"type": "tool_call", "name": call["name"], "arguments": call["arguments"], "step": step}
|
|
70
|
+
result = dispatch(sandbox, call["name"], call["arguments"])
|
|
71
|
+
yield {"type": "tool_result", "name": call["name"], "ok": result.ok,
|
|
72
|
+
"output": result.as_text(), "files": result.files, "step": step}
|
|
73
|
+
convo.append({"role": "user", "content": f"<tool_result>\n{result.as_text()}\n</tool_result>"})
|
|
74
|
+
|
|
75
|
+
yield {"type": "tool_limit", "max_steps": max_steps}
|
|
76
|
+
yield {"type": "done"}
|