prysm1 0.1.0__tar.gz
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.
- prysm1-0.1.0/.gitignore +52 -0
- prysm1-0.1.0/PKG-INFO +114 -0
- prysm1-0.1.0/README.md +95 -0
- prysm1-0.1.0/pyproject.toml +35 -0
- prysm1-0.1.0/src/prysm/__init__.py +31 -0
- prysm1-0.1.0/src/prysm/brain.py +208 -0
- prysm1-0.1.0/src/prysm/cli.py +314 -0
- prysm1-0.1.0/src/prysm/client.py +335 -0
- prysm1-0.1.0/src/prysm/extensions.py +79 -0
- prysm1-0.1.0/tests/test_sdk.py +309 -0
prysm1-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Environment
|
|
2
|
+
.env
|
|
3
|
+
*.env.local
|
|
4
|
+
|
|
5
|
+
# Python
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# Node
|
|
15
|
+
node_modules/
|
|
16
|
+
.next/
|
|
17
|
+
out/
|
|
18
|
+
|
|
19
|
+
# IDE
|
|
20
|
+
.vscode/
|
|
21
|
+
.idea/
|
|
22
|
+
*.swp
|
|
23
|
+
*.swo
|
|
24
|
+
.DS_Store
|
|
25
|
+
|
|
26
|
+
# Docker
|
|
27
|
+
*.log
|
|
28
|
+
|
|
29
|
+
# Outputs
|
|
30
|
+
*.tar.gz
|
|
31
|
+
|
|
32
|
+
# Database (SQLite — local/runtime state, never commit)
|
|
33
|
+
*.db
|
|
34
|
+
*.db-wal
|
|
35
|
+
*.db-shm
|
|
36
|
+
*.sqlite
|
|
37
|
+
*.sqlite3
|
|
38
|
+
|
|
39
|
+
# Benchmark data (fetched on demand; third-party licenses, e.g. AIME CC BY-NC-SA 4.0).
|
|
40
|
+
# We ship NO benchmark data in the repo — fetch_aime.py downloads it here at run time.
|
|
41
|
+
backend/eval/data/
|
|
42
|
+
eval/data/
|
|
43
|
+
|
|
44
|
+
# ── Docs internos sensibles (IP pre-patente) ──
|
|
45
|
+
# Excluidos por precaución (claims de patente sin presentar). Borra estas 2 líneas
|
|
46
|
+
# si quieres versionarlos en tu repo PRIVADO.
|
|
47
|
+
PRYSM-1-MODELO-DE-NEGOCIO.md
|
|
48
|
+
PRYSM-IP-OPORTUNIDADES.md
|
|
49
|
+
|
|
50
|
+
# Fixture huérfano de eval (ningún código lo referencia). Se mantiene local pero
|
|
51
|
+
# se destrackea para adelgazar el repo; recuperable del historial si se necesita.
|
|
52
|
+
backend/eval/_PILOT_test.pdf
|
prysm1-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prysm1
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: PRYSM — universal AI routing, OpenAI drop-in. One API, every model, always the best one.
|
|
5
|
+
Project-URL: Homepage, https://prysm1.com
|
|
6
|
+
Project-URL: Documentation, https://docs.prysm1.com
|
|
7
|
+
Author: PRYSM
|
|
8
|
+
License: Proprietary
|
|
9
|
+
Keywords: agents,ai,gateway,llm,openai,prysm,routing
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Requires-Dist: httpx>=0.27
|
|
15
|
+
Requires-Dist: openai>=1.40
|
|
16
|
+
Provides-Extra: yaml
|
|
17
|
+
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# prysm1 — PRYSM Python SDK
|
|
21
|
+
|
|
22
|
+
**One API, every model, always the best one.** An OpenAI drop-in: change one line,
|
|
23
|
+
get intelligent routing across 21 models, cost guardrails (AgentGuard), declarative
|
|
24
|
+
config (BRAIN.md), and cryptographic receipts (PrysmProof).
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install prysm1
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Drop-in replacement for OpenAI
|
|
31
|
+
|
|
32
|
+
`Prysm` subclasses `openai.OpenAI`, so all your existing code works unchanged.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from prysm import Prysm
|
|
36
|
+
|
|
37
|
+
client = Prysm() # api_key from $PRYSM_API_KEY, base_url from $PRYSM_BASE_URL
|
|
38
|
+
|
|
39
|
+
resp = client.chat.completions.create(
|
|
40
|
+
model="auto", # let PRYSM pick the best model
|
|
41
|
+
messages=[{"role": "user", "content": "Write a Python quicksort"}],
|
|
42
|
+
)
|
|
43
|
+
print(resp.choices[0].message.content)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Access the PRYSM extensions
|
|
47
|
+
|
|
48
|
+
Every response carries a `prysm` block (routing, cost, latency, proof). Use
|
|
49
|
+
`extension()` for clean dot-access:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import prysm
|
|
53
|
+
|
|
54
|
+
ext = prysm.extension(resp)
|
|
55
|
+
print(ext.routing.model_display) # "DeepSeek V3.2"
|
|
56
|
+
print(ext.cost.total_usd) # 0.000042
|
|
57
|
+
print(ext.proof.proof_hash) # "sha256:a1b2c3d4..."
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## BRAIN.md auto-discovery
|
|
61
|
+
|
|
62
|
+
Drop a `BRAIN.md` in your project root and the SDK finds it automatically, applying
|
|
63
|
+
your routing rules / cost caps / blocked models on every `complete()` call:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
client = Prysm() # discovers ./BRAIN.md (walks up from cwd)
|
|
67
|
+
resp = client.complete("Summarize this contract") # BRAIN.md applied
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Point it explicitly, pass a dict, or disable:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
Prysm(brain="path/to/BRAIN.md")
|
|
74
|
+
Prysm(brain={"max_cost": 0.005, "model": "deepseek-v3.2"})
|
|
75
|
+
Prysm(brain=None) # ignore BRAIN.md
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Validate a BRAIN.md before shipping:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
print(client.validate_brain(open("BRAIN.md").read()))
|
|
82
|
+
# {'valid': True, 'errors': [], 'warnings': [], 'normalized': {...}}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Helpers
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
client.route("debug this function") # dry-run: which model, est. cost (no call)
|
|
89
|
+
client.usage() # your usage stats
|
|
90
|
+
client.savings(baseline="gpt-5.2") # est. $ saved vs. an all-premium baseline
|
|
91
|
+
client.verify_proof(request_id) # verify a PrysmProof
|
|
92
|
+
client.models_catalog() # 21 models with pricing
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Configuration
|
|
96
|
+
|
|
97
|
+
| Setting | Argument | Env var | Default |
|
|
98
|
+
|---------|----------|---------|---------|
|
|
99
|
+
| API key | `api_key=` | `PRYSM_API_KEY` | — |
|
|
100
|
+
| Base URL | `base_url=` | `PRYSM_BASE_URL` | `https://api.prysm1.com/v1` |
|
|
101
|
+
| BRAIN.md | `brain=` | — | `"auto"` (discover) |
|
|
102
|
+
|
|
103
|
+
Get an API key at [prysm1.com](https://prysm1.com). Full docs at
|
|
104
|
+
[docs.prysm1.com](https://docs.prysm1.com).
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python tests/test_sdk.py # no pytest required; runs against the backend in-process
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
Proprietary.
|
prysm1-0.1.0/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# prysm1 — PRYSM Python SDK
|
|
2
|
+
|
|
3
|
+
**One API, every model, always the best one.** An OpenAI drop-in: change one line,
|
|
4
|
+
get intelligent routing across 21 models, cost guardrails (AgentGuard), declarative
|
|
5
|
+
config (BRAIN.md), and cryptographic receipts (PrysmProof).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install prysm1
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Drop-in replacement for OpenAI
|
|
12
|
+
|
|
13
|
+
`Prysm` subclasses `openai.OpenAI`, so all your existing code works unchanged.
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from prysm import Prysm
|
|
17
|
+
|
|
18
|
+
client = Prysm() # api_key from $PRYSM_API_KEY, base_url from $PRYSM_BASE_URL
|
|
19
|
+
|
|
20
|
+
resp = client.chat.completions.create(
|
|
21
|
+
model="auto", # let PRYSM pick the best model
|
|
22
|
+
messages=[{"role": "user", "content": "Write a Python quicksort"}],
|
|
23
|
+
)
|
|
24
|
+
print(resp.choices[0].message.content)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Access the PRYSM extensions
|
|
28
|
+
|
|
29
|
+
Every response carries a `prysm` block (routing, cost, latency, proof). Use
|
|
30
|
+
`extension()` for clean dot-access:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import prysm
|
|
34
|
+
|
|
35
|
+
ext = prysm.extension(resp)
|
|
36
|
+
print(ext.routing.model_display) # "DeepSeek V3.2"
|
|
37
|
+
print(ext.cost.total_usd) # 0.000042
|
|
38
|
+
print(ext.proof.proof_hash) # "sha256:a1b2c3d4..."
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## BRAIN.md auto-discovery
|
|
42
|
+
|
|
43
|
+
Drop a `BRAIN.md` in your project root and the SDK finds it automatically, applying
|
|
44
|
+
your routing rules / cost caps / blocked models on every `complete()` call:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
client = Prysm() # discovers ./BRAIN.md (walks up from cwd)
|
|
48
|
+
resp = client.complete("Summarize this contract") # BRAIN.md applied
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Point it explicitly, pass a dict, or disable:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
Prysm(brain="path/to/BRAIN.md")
|
|
55
|
+
Prysm(brain={"max_cost": 0.005, "model": "deepseek-v3.2"})
|
|
56
|
+
Prysm(brain=None) # ignore BRAIN.md
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Validate a BRAIN.md before shipping:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
print(client.validate_brain(open("BRAIN.md").read()))
|
|
63
|
+
# {'valid': True, 'errors': [], 'warnings': [], 'normalized': {...}}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Helpers
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
client.route("debug this function") # dry-run: which model, est. cost (no call)
|
|
70
|
+
client.usage() # your usage stats
|
|
71
|
+
client.savings(baseline="gpt-5.2") # est. $ saved vs. an all-premium baseline
|
|
72
|
+
client.verify_proof(request_id) # verify a PrysmProof
|
|
73
|
+
client.models_catalog() # 21 models with pricing
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Configuration
|
|
77
|
+
|
|
78
|
+
| Setting | Argument | Env var | Default |
|
|
79
|
+
|---------|----------|---------|---------|
|
|
80
|
+
| API key | `api_key=` | `PRYSM_API_KEY` | — |
|
|
81
|
+
| Base URL | `base_url=` | `PRYSM_BASE_URL` | `https://api.prysm1.com/v1` |
|
|
82
|
+
| BRAIN.md | `brain=` | — | `"auto"` (discover) |
|
|
83
|
+
|
|
84
|
+
Get an API key at [prysm1.com](https://prysm1.com). Full docs at
|
|
85
|
+
[docs.prysm1.com](https://docs.prysm1.com).
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
python tests/test_sdk.py # no pytest required; runs against the backend in-process
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
Proprietary.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "prysm1"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "PRYSM — universal AI routing, OpenAI drop-in. One API, every model, always the best one."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "Proprietary" }
|
|
12
|
+
authors = [{ name = "PRYSM" }]
|
|
13
|
+
keywords = ["ai", "llm", "routing", "openai", "gateway", "prysm", "agents"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"openai>=1.40",
|
|
21
|
+
"httpx>=0.27",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
yaml = ["pyyaml>=6.0"]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
prysm = "prysm.cli:main"
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://prysm1.com"
|
|
32
|
+
Documentation = "https://docs.prysm1.com"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.wheel]
|
|
35
|
+
packages = ["src/prysm"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
prysm1 — PRYSM Python SDK.
|
|
3
|
+
|
|
4
|
+
One API, every model, always the best one. An OpenAI drop-in:
|
|
5
|
+
|
|
6
|
+
from prysm import Prysm
|
|
7
|
+
client = Prysm()
|
|
8
|
+
resp = client.chat.completions.create(
|
|
9
|
+
model="auto",
|
|
10
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
11
|
+
)
|
|
12
|
+
print(prysm.extension(resp).routing.model_display)
|
|
13
|
+
"""
|
|
14
|
+
from .brain import find_brain, load_brain, normalize_brain, parse_brain
|
|
15
|
+
from .client import Prysm
|
|
16
|
+
from .extensions import cost, extension, proof, routing
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.0"
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"Prysm",
|
|
22
|
+
"extension",
|
|
23
|
+
"routing",
|
|
24
|
+
"cost",
|
|
25
|
+
"proof",
|
|
26
|
+
"load_brain",
|
|
27
|
+
"find_brain",
|
|
28
|
+
"parse_brain",
|
|
29
|
+
"normalize_brain",
|
|
30
|
+
"__version__",
|
|
31
|
+
]
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""
|
|
2
|
+
prysm.brain — client-side BRAIN.md discovery + parsing.
|
|
3
|
+
|
|
4
|
+
Mirrors the server's parser so the SDK can auto-discover a project's BRAIN.md and
|
|
5
|
+
inject it into requests without a network round-trip. Uses PyYAML when present,
|
|
6
|
+
falls back to a dependency-free subset parser.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
# BRAIN.md 'when' vocabulary -> canonical router signals.
|
|
14
|
+
SIGNAL_ALIASES = {
|
|
15
|
+
"code": "code", "coding": "code", "programming": "code", "dev": "code",
|
|
16
|
+
"write": "write", "writing": "write", "content": "write", "copy": "write", "copywriting": "write",
|
|
17
|
+
"analysis": "analysis", "research": "analysis", "analyze": "analysis", "analyse": "analysis",
|
|
18
|
+
"math": "math", "maths": "math", "calculation": "math", "calc": "math",
|
|
19
|
+
"translate": "translate", "translation": "translate", "i18n": "translate",
|
|
20
|
+
"realtime": "realtime", "real-time": "realtime", "news": "realtime", "live": "realtime",
|
|
21
|
+
"simple": "simple", "quick": "simple", "lookup": "simple",
|
|
22
|
+
"multimodal": "multimodal", "vision": "multimodal", "image": "multimodal", "images": "multimodal",
|
|
23
|
+
"reasoning": "reasoning", "logic": "reasoning", "reason": "reasoning",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _split_commas(s: str) -> list:
|
|
28
|
+
out, buf, q = [], "", None
|
|
29
|
+
for ch in s:
|
|
30
|
+
if q:
|
|
31
|
+
buf += ch
|
|
32
|
+
if ch == q:
|
|
33
|
+
q = None
|
|
34
|
+
elif ch in ("'", '"'):
|
|
35
|
+
q = ch
|
|
36
|
+
buf += ch
|
|
37
|
+
elif ch == ",":
|
|
38
|
+
out.append(buf)
|
|
39
|
+
buf = ""
|
|
40
|
+
else:
|
|
41
|
+
buf += ch
|
|
42
|
+
if buf.strip():
|
|
43
|
+
out.append(buf)
|
|
44
|
+
return out
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _coerce(v: str) -> Any:
|
|
48
|
+
s = v.strip()
|
|
49
|
+
if not s:
|
|
50
|
+
return ""
|
|
51
|
+
if s.startswith("[") and s.endswith("]"):
|
|
52
|
+
inner = s[1:-1].strip()
|
|
53
|
+
return [] if not inner else [_coerce(x) for x in _split_commas(inner)]
|
|
54
|
+
if len(s) >= 2 and s[0] == s[-1] and s[0] in ("'", '"'):
|
|
55
|
+
return s[1:-1]
|
|
56
|
+
low = s.lower()
|
|
57
|
+
if low in ("true", "yes", "on"):
|
|
58
|
+
return True
|
|
59
|
+
if low in ("false", "no", "off"):
|
|
60
|
+
return False
|
|
61
|
+
if low in ("null", "none", "~"):
|
|
62
|
+
return None
|
|
63
|
+
try:
|
|
64
|
+
return float(s) if ("." in s or "e" in low) else int(s)
|
|
65
|
+
except ValueError:
|
|
66
|
+
return s
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _strip_inline_comment(line: str) -> str:
|
|
70
|
+
q = None
|
|
71
|
+
for i, ch in enumerate(line):
|
|
72
|
+
if q:
|
|
73
|
+
if ch == q:
|
|
74
|
+
q = None
|
|
75
|
+
elif ch in ("'", '"'):
|
|
76
|
+
q = ch
|
|
77
|
+
elif ch == "#" and (i == 0 or line[i - 1] in (" ", "\t")):
|
|
78
|
+
return line[:i]
|
|
79
|
+
return line
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _parse_subset(text: str) -> dict:
|
|
83
|
+
data: dict = {}
|
|
84
|
+
cur_key = None
|
|
85
|
+
cur_list = None
|
|
86
|
+
cur_dict = None
|
|
87
|
+
for raw in text.splitlines():
|
|
88
|
+
line = _strip_inline_comment(raw)
|
|
89
|
+
if not line.strip():
|
|
90
|
+
continue
|
|
91
|
+
stripped = line.strip()
|
|
92
|
+
indent = len(line) - len(line.lstrip())
|
|
93
|
+
if stripped.startswith("- "):
|
|
94
|
+
item = stripped[2:].strip()
|
|
95
|
+
if cur_list is None:
|
|
96
|
+
cur_list = []
|
|
97
|
+
if cur_key is not None:
|
|
98
|
+
data[cur_key] = cur_list
|
|
99
|
+
if ":" in item and item[0] not in ("'", '"'):
|
|
100
|
+
k, _, v = item.partition(":")
|
|
101
|
+
cur_dict = {k.strip(): _coerce(v)}
|
|
102
|
+
cur_list.append(cur_dict)
|
|
103
|
+
else:
|
|
104
|
+
cur_dict = None
|
|
105
|
+
cur_list.append(_coerce(item))
|
|
106
|
+
continue
|
|
107
|
+
if indent > 0 and cur_dict is not None and ":" in stripped:
|
|
108
|
+
k, _, v = stripped.partition(":")
|
|
109
|
+
cur_dict[k.strip()] = _coerce(v)
|
|
110
|
+
continue
|
|
111
|
+
if ":" in stripped:
|
|
112
|
+
k, _, v = stripped.partition(":")
|
|
113
|
+
cur_key = k.strip()
|
|
114
|
+
cur_list = None
|
|
115
|
+
cur_dict = None
|
|
116
|
+
data[cur_key] = _coerce(v.strip()) if v.strip() != "" else None
|
|
117
|
+
return data
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def parse_brain(text: str) -> dict:
|
|
121
|
+
"""Parse BRAIN.md text into a raw mapping. Uses PyYAML when available."""
|
|
122
|
+
if not text or not text.strip():
|
|
123
|
+
return {}
|
|
124
|
+
try:
|
|
125
|
+
import yaml # type: ignore
|
|
126
|
+
data = yaml.safe_load(text)
|
|
127
|
+
if isinstance(data, dict):
|
|
128
|
+
return data
|
|
129
|
+
except Exception:
|
|
130
|
+
pass
|
|
131
|
+
return _parse_subset(text)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def normalize_brain(raw: dict) -> dict:
|
|
135
|
+
"""Map a raw BRAIN.md mapping to the canonical config the router consumes."""
|
|
136
|
+
raw = raw or {}
|
|
137
|
+
n: dict = {}
|
|
138
|
+
if raw.get("name"):
|
|
139
|
+
n["name"] = raw["name"]
|
|
140
|
+
if raw.get("version") is not None:
|
|
141
|
+
n["version"] = raw["version"]
|
|
142
|
+
if raw.get("model"):
|
|
143
|
+
n["model"] = raw["model"]
|
|
144
|
+
cap = raw.get("max_cost", raw.get("max_cost_per_request"))
|
|
145
|
+
if cap is not None:
|
|
146
|
+
n["max_cost"] = cap
|
|
147
|
+
if raw.get("monthly_budget") is not None:
|
|
148
|
+
n["monthly_budget"] = raw["monthly_budget"]
|
|
149
|
+
rules = []
|
|
150
|
+
for r in (raw.get("rules") or []):
|
|
151
|
+
if isinstance(r, dict) and r.get("when") and r.get("model"):
|
|
152
|
+
w = str(r["when"]).strip().lower()
|
|
153
|
+
rules.append({
|
|
154
|
+
"when": SIGNAL_ALIASES.get(w, w),
|
|
155
|
+
"model": r["model"],
|
|
156
|
+
"reason": r.get("reason", ""),
|
|
157
|
+
})
|
|
158
|
+
if rules:
|
|
159
|
+
n["rules"] = rules
|
|
160
|
+
if raw.get("blocked"):
|
|
161
|
+
n["blocked"] = [str(x) for x in raw["blocked"]]
|
|
162
|
+
if raw.get("fallback"):
|
|
163
|
+
n["fallback"] = [str(x) for x in raw["fallback"]]
|
|
164
|
+
if raw.get("quality_threshold") is not None:
|
|
165
|
+
n["quality_threshold"] = raw["quality_threshold"]
|
|
166
|
+
if raw.get("quality_signals"):
|
|
167
|
+
n["quality_signals"] = raw["quality_signals"]
|
|
168
|
+
if raw.get("log_level"):
|
|
169
|
+
n["log_level"] = raw["log_level"]
|
|
170
|
+
if raw.get("log_proofs") is not None:
|
|
171
|
+
n["log_proofs"] = raw["log_proofs"]
|
|
172
|
+
return n
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def find_brain(start: Optional[str] = None) -> Optional[str]:
|
|
176
|
+
"""Walk up from `start` (or cwd) looking for a BRAIN.md. Returns its path or None."""
|
|
177
|
+
d = os.path.abspath(start or os.getcwd())
|
|
178
|
+
if os.path.isfile(d): # a file was passed
|
|
179
|
+
return d if os.path.basename(d) == "BRAIN.md" else None
|
|
180
|
+
while True:
|
|
181
|
+
candidate = os.path.join(d, "BRAIN.md")
|
|
182
|
+
if os.path.isfile(candidate):
|
|
183
|
+
return candidate
|
|
184
|
+
parent = os.path.dirname(d)
|
|
185
|
+
if parent == d:
|
|
186
|
+
return None
|
|
187
|
+
d = parent
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def load_brain(path_or_dir: Optional[str] = None) -> Optional[dict]:
|
|
191
|
+
"""Discover (or read) a BRAIN.md and return its normalized config, or None.
|
|
192
|
+
|
|
193
|
+
- None -> auto-discover by walking up from cwd
|
|
194
|
+
- a directory -> auto-discover from there
|
|
195
|
+
- a file path -> read that file
|
|
196
|
+
"""
|
|
197
|
+
path = path_or_dir
|
|
198
|
+
if path is None or os.path.isdir(path or ""):
|
|
199
|
+
path = find_brain(path)
|
|
200
|
+
if not path or not os.path.isfile(path):
|
|
201
|
+
return None
|
|
202
|
+
try:
|
|
203
|
+
with open(path, encoding="utf-8") as f:
|
|
204
|
+
text = f.read()
|
|
205
|
+
except OSError:
|
|
206
|
+
return None
|
|
207
|
+
normalized = normalize_brain(parse_brain(text))
|
|
208
|
+
return normalized or None
|