instructvault 0.2.1__py3-none-any.whl → 0.2.3__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.
- instructvault/bundle.py +13 -4
- instructvault/cli.py +9 -5
- instructvault/io.py +6 -3
- instructvault/sdk.py +3 -1
- instructvault/spec.py +17 -1
- {instructvault-0.2.1.dist-info → instructvault-0.2.3.dist-info}/METADATA +20 -1
- instructvault-0.2.3.dist-info/RECORD +17 -0
- instructvault-0.2.1.dist-info/RECORD +0 -17
- {instructvault-0.2.1.dist-info → instructvault-0.2.3.dist-info}/WHEEL +0 -0
- {instructvault-0.2.1.dist-info → instructvault-0.2.3.dist-info}/entry_points.txt +0 -0
- {instructvault-0.2.1.dist-info → instructvault-0.2.3.dist-info}/licenses/LICENSE +0 -0
instructvault/bundle.py
CHANGED
|
@@ -29,22 +29,32 @@ def collect_prompts(repo_root: Path, prompts_dir: Path, ref: Optional[str]) -> L
|
|
|
29
29
|
store = PromptStore(repo_root)
|
|
30
30
|
prompts: List[BundlePrompt] = []
|
|
31
31
|
if ref is None:
|
|
32
|
+
if not prompts_dir.exists():
|
|
33
|
+
raise FileNotFoundError(f"Prompts directory not found: {prompts_dir}")
|
|
34
|
+
try:
|
|
35
|
+
prompts_dir.relative_to(repo_root)
|
|
36
|
+
except Exception:
|
|
37
|
+
raise ValueError("prompts_dir must be within repo_root")
|
|
32
38
|
for p in sorted(prompts_dir.rglob("*.prompt.y*ml")):
|
|
33
39
|
rel_path = p.relative_to(repo_root).as_posix()
|
|
34
|
-
spec = load_prompt_spec(p.read_text(encoding="utf-8"))
|
|
40
|
+
spec = load_prompt_spec(p.read_text(encoding="utf-8"), allow_no_tests=True)
|
|
35
41
|
prompts.append(BundlePrompt(rel_path, spec))
|
|
36
42
|
for p in sorted(prompts_dir.rglob("*.prompt.json")):
|
|
37
43
|
rel_path = p.relative_to(repo_root).as_posix()
|
|
38
|
-
spec = load_prompt_spec(p.read_text(encoding="utf-8"))
|
|
44
|
+
spec = load_prompt_spec(p.read_text(encoding="utf-8"), allow_no_tests=True)
|
|
39
45
|
prompts.append(BundlePrompt(rel_path, spec))
|
|
46
|
+
if not prompts:
|
|
47
|
+
raise ValueError(f"No prompt files found in {prompts_dir}")
|
|
40
48
|
return prompts
|
|
41
49
|
|
|
42
50
|
rel_dir = prompts_dir.relative_to(repo_root).as_posix()
|
|
43
51
|
for rel_path in _list_files_at_ref(repo_root, ref, rel_dir):
|
|
44
52
|
if not _is_prompt_file(rel_path):
|
|
45
53
|
continue
|
|
46
|
-
spec = load_prompt_spec(store.read_text(rel_path, ref=ref))
|
|
54
|
+
spec = load_prompt_spec(store.read_text(rel_path, ref=ref), allow_no_tests=True)
|
|
47
55
|
prompts.append(BundlePrompt(rel_path, spec))
|
|
56
|
+
if not prompts:
|
|
57
|
+
raise ValueError(f"No prompt files found at ref {ref} in {rel_dir}")
|
|
48
58
|
return prompts
|
|
49
59
|
|
|
50
60
|
def write_bundle(out_path: Path, *, repo_root: Path, prompts_dir: Path, ref: Optional[str]) -> None:
|
|
@@ -59,4 +69,3 @@ def write_bundle(out_path: Path, *, repo_root: Path, prompts_dir: Path, ref: Opt
|
|
|
59
69
|
}
|
|
60
70
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
61
71
|
out_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
62
|
-
|
instructvault/cli.py
CHANGED
|
@@ -39,7 +39,7 @@ def validate(path: Path = typer.Argument(...),
|
|
|
39
39
|
results = []
|
|
40
40
|
for f in files:
|
|
41
41
|
try:
|
|
42
|
-
spec = load_prompt_spec(f.read_text(encoding="utf-8"))
|
|
42
|
+
spec = load_prompt_spec(f.read_text(encoding="utf-8"), allow_no_tests=False)
|
|
43
43
|
try:
|
|
44
44
|
rel_path = f.relative_to(repo).as_posix()
|
|
45
45
|
except ValueError:
|
|
@@ -65,10 +65,14 @@ def render(prompt_path: str = typer.Argument(...),
|
|
|
65
65
|
vars_json: str = typer.Option("{}", "--vars"),
|
|
66
66
|
ref: Optional[str] = typer.Option(None, "--ref"),
|
|
67
67
|
repo: Path = typer.Option(Path("."), "--repo"),
|
|
68
|
-
json_out: bool = typer.Option(False, "--json")
|
|
68
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
69
|
+
allow_no_tests: bool = typer.Option(False, "--allow-no-tests")):
|
|
69
70
|
store = PromptStore(repo_root=repo)
|
|
70
|
-
spec = load_prompt_spec(store.read_text(prompt_path, ref=ref))
|
|
71
|
-
|
|
71
|
+
spec = load_prompt_spec(store.read_text(prompt_path, ref=ref), allow_no_tests=allow_no_tests)
|
|
72
|
+
try:
|
|
73
|
+
vars_dict = json.loads(vars_json)
|
|
74
|
+
except Exception:
|
|
75
|
+
raise typer.BadParameter("Invalid JSON for --vars")
|
|
72
76
|
check_required_vars(spec, vars_dict)
|
|
73
77
|
msgs = render_messages(spec, vars_dict)
|
|
74
78
|
if json_out:
|
|
@@ -121,7 +125,7 @@ def eval(prompt_path: str = typer.Argument(...),
|
|
|
121
125
|
repo: Path = typer.Option(Path("."), "--repo"),
|
|
122
126
|
json_out: bool = typer.Option(False, "--json")):
|
|
123
127
|
store = PromptStore(repo_root=repo)
|
|
124
|
-
spec = load_prompt_spec(store.read_text(prompt_path, ref=ref))
|
|
128
|
+
spec = load_prompt_spec(store.read_text(prompt_path, ref=ref), allow_no_tests=False)
|
|
125
129
|
|
|
126
130
|
ok1, r1 = run_inline_tests(spec)
|
|
127
131
|
results = list(r1)
|
instructvault/io.py
CHANGED
|
@@ -4,13 +4,16 @@ import json
|
|
|
4
4
|
import yaml
|
|
5
5
|
from .spec import DatasetRow, PromptSpec
|
|
6
6
|
|
|
7
|
-
def load_prompt_spec(yaml_text: str) -> PromptSpec:
|
|
7
|
+
def load_prompt_spec(yaml_text: str, *, allow_no_tests: bool = True) -> PromptSpec:
|
|
8
8
|
text = yaml_text.strip()
|
|
9
9
|
if text.startswith("{") or text.startswith("["):
|
|
10
|
-
|
|
10
|
+
try:
|
|
11
|
+
data: Dict[str, Any] = json.loads(text) if text else {}
|
|
12
|
+
except Exception:
|
|
13
|
+
data = yaml.safe_load(yaml_text) or {}
|
|
11
14
|
else:
|
|
12
15
|
data = yaml.safe_load(yaml_text) or {}
|
|
13
|
-
return PromptSpec.model_validate(data)
|
|
16
|
+
return PromptSpec.model_validate(data, context={"allow_no_tests": allow_no_tests})
|
|
14
17
|
|
|
15
18
|
def load_dataset_jsonl(text: str) -> List[DatasetRow]:
|
|
16
19
|
rows: List[DatasetRow] = []
|
instructvault/sdk.py
CHANGED
|
@@ -19,12 +19,14 @@ class InstructVault:
|
|
|
19
19
|
|
|
20
20
|
def load_prompt(self, prompt_path: str, ref: Optional[str] = None) -> PromptSpec:
|
|
21
21
|
if self.bundle is not None:
|
|
22
|
+
if ref is not None:
|
|
23
|
+
raise ValueError("ref is not supported when using bundle_path")
|
|
22
24
|
if prompt_path not in self.bundle:
|
|
23
25
|
raise FileNotFoundError(f"Prompt not found in bundle: {prompt_path}")
|
|
24
26
|
return self.bundle[prompt_path]
|
|
25
27
|
if self.store is None:
|
|
26
28
|
raise ValueError("No repo_root configured")
|
|
27
|
-
return load_prompt_spec(self.store.read_text(prompt_path, ref=ref))
|
|
29
|
+
return load_prompt_spec(self.store.read_text(prompt_path, ref=ref), allow_no_tests=True)
|
|
28
30
|
def render(self, prompt_path: str, vars: Dict[str, Any], ref: Optional[str] = None) -> List[PromptMessage]:
|
|
29
31
|
spec = self.load_prompt(prompt_path, ref=ref)
|
|
30
32
|
check_required_vars(spec, vars)
|
instructvault/spec.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from typing import Any, Dict, List, Literal, Optional
|
|
3
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
4
4
|
|
|
5
5
|
Role = Literal["system", "user", "assistant", "tool"]
|
|
6
6
|
|
|
@@ -25,6 +25,11 @@ class AssertSpec(BaseModel):
|
|
|
25
25
|
contains_any: Optional[List[str]] = None
|
|
26
26
|
contains_all: Optional[List[str]] = None
|
|
27
27
|
not_contains: Optional[List[str]] = None
|
|
28
|
+
@model_validator(mode="after")
|
|
29
|
+
def _require_one(self) -> "AssertSpec":
|
|
30
|
+
if not (self.contains_any or self.contains_all or self.not_contains):
|
|
31
|
+
raise ValueError("assert must include at least one of contains_any, contains_all, not_contains")
|
|
32
|
+
return self
|
|
28
33
|
|
|
29
34
|
class PromptTest(BaseModel):
|
|
30
35
|
model_config = ConfigDict(extra="forbid")
|
|
@@ -42,6 +47,17 @@ class PromptSpec(BaseModel):
|
|
|
42
47
|
messages: List[PromptMessage]
|
|
43
48
|
tests: List[PromptTest] = Field(default_factory=list)
|
|
44
49
|
|
|
50
|
+
@model_validator(mode="after")
|
|
51
|
+
def _require_tests(self) -> "PromptSpec":
|
|
52
|
+
allow_no_tests = False
|
|
53
|
+
try:
|
|
54
|
+
allow_no_tests = bool(self.__pydantic_context__.get("allow_no_tests"))
|
|
55
|
+
except Exception:
|
|
56
|
+
allow_no_tests = False
|
|
57
|
+
if not self.tests and not allow_no_tests:
|
|
58
|
+
raise ValueError("prompt must include at least one test")
|
|
59
|
+
return self
|
|
60
|
+
|
|
45
61
|
class DatasetRow(BaseModel):
|
|
46
62
|
model_config = ConfigDict(extra="forbid")
|
|
47
63
|
vars: Dict[str, Any] = Field(default_factory=dict)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: instructvault
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Git-first prompt registry + CI evals + lightweight runtime SDK (ivault).
|
|
5
5
|
Project-URL: Homepage, https://github.com/05satyam/instruct_vault
|
|
6
6
|
Project-URL: Repository, https://github.com/05satyam/instruct_vault
|
|
@@ -14,6 +14,7 @@ Requires-Dist: pyyaml>=6.0
|
|
|
14
14
|
Requires-Dist: rich>=13.7
|
|
15
15
|
Requires-Dist: typer>=0.12
|
|
16
16
|
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
17
18
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
18
19
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
19
20
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
@@ -48,6 +49,10 @@ flowchart LR
|
|
|
48
49
|
Enterprises already have Git + PR reviews + CI/CD. Prompts usually don’t.
|
|
49
50
|
InstructVault brings **prompt‑as‑code** without requiring a server, database, or platform.
|
|
50
51
|
|
|
52
|
+
## Vision
|
|
53
|
+
Short version: Git‑first prompts with CI governance and zero‑latency runtime.
|
|
54
|
+
Full vision: `docs/vision.md`
|
|
55
|
+
|
|
51
56
|
## Features
|
|
52
57
|
- ✅ Git‑native versioning (tags/SHAs = releases)
|
|
53
58
|
- ✅ CLI‑first (`init`, `validate`, `render`, `eval`, `diff`, `resolve`, `bundle`)
|
|
@@ -128,6 +133,10 @@ ivault render prompts/support_reply.prompt.yml --vars '{"ticket_text":"My app cr
|
|
|
128
133
|
ivault eval prompts/support_reply.prompt.yml --dataset datasets/support_cases.jsonl --report out/report.json --junit out/junit.xml
|
|
129
134
|
```
|
|
130
135
|
|
|
136
|
+
Note: Prompts must include at least one inline test. Datasets are optional.
|
|
137
|
+
Migration tip: if you need to render a prompt that doesn’t yet include tests, use
|
|
138
|
+
`ivault render --allow-no-tests` or add a minimal test first.
|
|
139
|
+
|
|
131
140
|
### 5) Version prompts with tags
|
|
132
141
|
```bash
|
|
133
142
|
git add prompts datasets
|
|
@@ -176,7 +185,17 @@ export IVAULT_REPO_ROOT=/path/to/your/repo
|
|
|
176
185
|
PYTHONPATH=. uvicorn ivault_playground.app:app --reload
|
|
177
186
|
```
|
|
178
187
|
|
|
188
|
+

|
|
189
|
+
|
|
190
|
+
Optional auth:
|
|
191
|
+
```bash
|
|
192
|
+
export IVAULT_PLAYGROUND_API_KEY=your-secret
|
|
193
|
+
```
|
|
194
|
+
Then send `x-ivault-api-key` in requests (or keep it behind your org gateway).
|
|
195
|
+
If you don’t set the env var, no auth is required.
|
|
196
|
+
|
|
179
197
|
## Docs
|
|
198
|
+
- `docs/vision.md`
|
|
180
199
|
- `docs/governance.md`
|
|
181
200
|
- `docs/ci.md`
|
|
182
201
|
- `docs/playground.md`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
instructvault/__init__.py,sha256=cg7j0qh6W84D-K0uSOLKKAP2JquW4NRXwZRDDLk5E18,59
|
|
2
|
+
instructvault/bundle.py,sha256=6bfHNxJsE3zuZBLX5ZiMAhn1Dw6BnFHRa55fN6XIPRI,3008
|
|
3
|
+
instructvault/cli.py,sha256=v5vP-sgVpXRs-YGvxH8VWIarFqUD1IsXdB9lseaFJDA,6310
|
|
4
|
+
instructvault/diff.py,sha256=vz_vmKDXasNFoVKHCk2u_TsboHk1BdwvX0wCnJI1ATQ,252
|
|
5
|
+
instructvault/eval.py,sha256=-yrFHCEUrONvzfKLP8s_RktFU74Ergp9tQJvzfrMR9s,1949
|
|
6
|
+
instructvault/io.py,sha256=n1yQfiy93Duz-8tJ_HpbCEq8MUn2jlLpSmUY6XBg8G4,1037
|
|
7
|
+
instructvault/junit.py,sha256=sIEcIiGD3Xk6uCYjnE5p_07j8dPoS_RAc2eoy3BIBeQ,1133
|
|
8
|
+
instructvault/render.py,sha256=vcVnqIXGytskZEKbUofoKgIVflQSYhsmdpEtZs1X19A,919
|
|
9
|
+
instructvault/scaffold.py,sha256=f5gwXE3dUPuJYTedZRqBs8w5SQEgt1dgDSuqW2dxrMg,1685
|
|
10
|
+
instructvault/sdk.py,sha256=abqFrmc9Q5LUqC_ZrwM12DlpTZZkXqRuzN0T2x9lqqY,1727
|
|
11
|
+
instructvault/spec.py,sha256=ZtVXosHy0f3hRB5CP9xbVzSdW8fDnf0-AR46ehG9-MA,2450
|
|
12
|
+
instructvault/store.py,sha256=NhN49w7xrkeij0lQDr-CEdANYLpNVBXumv_cKqLmiYY,1056
|
|
13
|
+
instructvault-0.2.3.dist-info/METADATA,sha256=1fSmJLHruAYXuZpJa6AbDVSDaG8SLp8_lrxlH3OHaEM,6205
|
|
14
|
+
instructvault-0.2.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
15
|
+
instructvault-0.2.3.dist-info/entry_points.txt,sha256=cdcMJQwBk9c95LwfN2W6x2xO43FwPjhfV3jHE7TTuHg,49
|
|
16
|
+
instructvault-0.2.3.dist-info/licenses/LICENSE,sha256=VFbCvIsyizmkz4NrZPMdcPhyRK5uM0HhAjv3GBUbb7Y,135
|
|
17
|
+
instructvault-0.2.3.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
instructvault/__init__.py,sha256=cg7j0qh6W84D-K0uSOLKKAP2JquW4NRXwZRDDLk5E18,59
|
|
2
|
-
instructvault/bundle.py,sha256=gXOqjpza_8MrYJjJVCzBJKHbO78bXkAbqPUjKnS_6Nk,2478
|
|
3
|
-
instructvault/cli.py,sha256=2ylpvw-GHmZdIUVS3AoepWcQy6RG9QAwqVEQaCFG60o,6065
|
|
4
|
-
instructvault/diff.py,sha256=vz_vmKDXasNFoVKHCk2u_TsboHk1BdwvX0wCnJI1ATQ,252
|
|
5
|
-
instructvault/eval.py,sha256=-yrFHCEUrONvzfKLP8s_RktFU74Ergp9tQJvzfrMR9s,1949
|
|
6
|
-
instructvault/io.py,sha256=mxoNp6SXbFoty22fzrbf7z-B5Nlw0XjgWphK0awA1S8,867
|
|
7
|
-
instructvault/junit.py,sha256=sIEcIiGD3Xk6uCYjnE5p_07j8dPoS_RAc2eoy3BIBeQ,1133
|
|
8
|
-
instructvault/render.py,sha256=vcVnqIXGytskZEKbUofoKgIVflQSYhsmdpEtZs1X19A,919
|
|
9
|
-
instructvault/scaffold.py,sha256=f5gwXE3dUPuJYTedZRqBs8w5SQEgt1dgDSuqW2dxrMg,1685
|
|
10
|
-
instructvault/sdk.py,sha256=JpEik6AZof2cIG-4GO-mdWwZe7w2rwq45jKawwfstsQ,1594
|
|
11
|
-
instructvault/spec.py,sha256=xRkcVjMnoKECLaAfjQHiGgQVMdZ_KvNzf-K-j6DQ6k0,1737
|
|
12
|
-
instructvault/store.py,sha256=NhN49w7xrkeij0lQDr-CEdANYLpNVBXumv_cKqLmiYY,1056
|
|
13
|
-
instructvault-0.2.1.dist-info/METADATA,sha256=rUEeRQAC23afX9UKccIqNKnGxCbKAmFaSQmnQxIaJew,5536
|
|
14
|
-
instructvault-0.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
15
|
-
instructvault-0.2.1.dist-info/entry_points.txt,sha256=cdcMJQwBk9c95LwfN2W6x2xO43FwPjhfV3jHE7TTuHg,49
|
|
16
|
-
instructvault-0.2.1.dist-info/licenses/LICENSE,sha256=VFbCvIsyizmkz4NrZPMdcPhyRK5uM0HhAjv3GBUbb7Y,135
|
|
17
|
-
instructvault-0.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|