socratic-engine 0.2.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.
- socratic_engine/__init__.py +59 -0
- socratic_engine/cli.py +182 -0
- socratic_engine/engine.py +904 -0
- socratic_engine/mcp_server.py +219 -0
- socratic_engine/tree.py +246 -0
- socratic_engine-0.2.0.dist-info/METADATA +971 -0
- socratic_engine-0.2.0.dist-info/RECORD +11 -0
- socratic_engine-0.2.0.dist-info/WHEEL +5 -0
- socratic_engine-0.2.0.dist-info/entry_points.txt +3 -0
- socratic_engine-0.2.0.dist-info/licenses/LICENSE +201 -0
- socratic_engine-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
socratic_engine — motor socrático recursivo con semántica epistemológica.
|
|
3
|
+
|
|
4
|
+
Externalized epistemic scaffolding for AI agents. Recursive boolean
|
|
5
|
+
certification trees.
|
|
6
|
+
|
|
7
|
+
El motor evalúa árboles booleanos recursivos (AND/OR/NOT/XOR/IMPLIES) con
|
|
8
|
+
lógica TRIVALUADA (TRUE/FALSE/UNKNOWN), certificación de evidencia
|
|
9
|
+
(certified: evidencia estructural ≠ opinión) y rastro completo de
|
|
10
|
+
razonamiento (explain()/diagnose()).
|
|
11
|
+
|
|
12
|
+
Agnóstico: no contiene lógica de dominio. Los predicates registrados son
|
|
13
|
+
deterministas (type_glob/type_prefix/type_regex/type_has/ctx_has); un LLM
|
|
14
|
+
u otro motor puede registrar los suyos, pero solo la evidencia estructural
|
|
15
|
+
certifica (R10: el LLM opina, no certifica).
|
|
16
|
+
|
|
17
|
+
Uso:
|
|
18
|
+
from socratic_engine import SocraticEngine, Truth, tree_home
|
|
19
|
+
eng = SocraticEngine()
|
|
20
|
+
ev = eng.evaluate({"op": "AND", "children": [
|
|
21
|
+
{"predicate": "type_prefix", "args": ["$type", "VSL-LANG-"]},
|
|
22
|
+
]}, {"type": "VSL-LANG-GATES-v1.0"})
|
|
23
|
+
# ev.truth == Truth.TRUE, ev.certified == True
|
|
24
|
+
|
|
25
|
+
CLI:
|
|
26
|
+
socratic-engine eval-tree <tree.vsm|tree.json> [--context <json>] [--doc-type <TYPE>]
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from .engine import (
|
|
30
|
+
Evaluation,
|
|
31
|
+
FailureTrace,
|
|
32
|
+
PredicateCache,
|
|
33
|
+
PredicateResult,
|
|
34
|
+
Predicate,
|
|
35
|
+
SocraticEngine,
|
|
36
|
+
Truth,
|
|
37
|
+
cached,
|
|
38
|
+
find_failure_traces,
|
|
39
|
+
)
|
|
40
|
+
from .tree import (
|
|
41
|
+
SocraticTreeBuilder,
|
|
42
|
+
parse_socratic_block,
|
|
43
|
+
tree_home,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"Evaluation",
|
|
48
|
+
"FailureTrace",
|
|
49
|
+
"Predicate",
|
|
50
|
+
"PredicateResult",
|
|
51
|
+
"SocraticEngine",
|
|
52
|
+
"SocraticTreeBuilder",
|
|
53
|
+
"Truth",
|
|
54
|
+
"find_failure_traces",
|
|
55
|
+
"parse_socratic_block",
|
|
56
|
+
"tree_home",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
__version__ = "0.2.0"
|
socratic_engine/cli.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
socratic_engine.cli — contrato externo del motor (CLI eval-tree + selftest).
|
|
3
|
+
|
|
4
|
+
El motor se expone por contrato externo estable para que cualquier
|
|
5
|
+
instrumento (gate write-time, plugin TS, MCP server, script CI/CD) lo
|
|
6
|
+
invocable sin importar el paquete Python internamente.
|
|
7
|
+
|
|
8
|
+
Uso:
|
|
9
|
+
socratic-engine eval-tree <tree.vsm|tree.json> [--context <json>] [--doc-type <TYPE>]
|
|
10
|
+
→ {"truth":"TRUE","certified":true,"home":"vsl-language","unknown":false,
|
|
11
|
+
"diagnose":[...],"explain":"..."}
|
|
12
|
+
|
|
13
|
+
Sin argumentos: ejecuta el selftest (R4.1: el instrumento se auto-verifica).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from .engine import PredicateResult, SocraticEngine, Truth
|
|
21
|
+
from .tree import SocraticTreeBuilder, parse_socratic_block, tree_home
|
|
22
|
+
|
|
23
|
+
def _run_selftest() -> None:
|
|
24
|
+
# selftest rápido (R4.1: el instrumento se auto-verifica)
|
|
25
|
+
eng = SocraticEngine()
|
|
26
|
+
t = {
|
|
27
|
+
"op": "AND",
|
|
28
|
+
"children": [
|
|
29
|
+
{"predicate": "type_prefix", "args": ["$type", "VSL-LANG-"]},
|
|
30
|
+
{"op": "NOT", "children": [{"predicate": "type_has", "args": ["$type", "INDEX"]}]},
|
|
31
|
+
],
|
|
32
|
+
}
|
|
33
|
+
ev = eng.evaluate(t, {"type": "VSL-LANG-GATES-v1.0"})
|
|
34
|
+
assert ev.is_true, "VSL-LANG-GATES debe ser TRUE"
|
|
35
|
+
assert ev.certified, "builtins deterministas deben certificar"
|
|
36
|
+
ev2 = eng.evaluate(t, {"type": "VSL-LANGUAGE-INDEX-v1.0"})
|
|
37
|
+
assert ev2.is_false, "INDEX debe ser FALSE"
|
|
38
|
+
|
|
39
|
+
# trivaluado: UNKNOWN propaga
|
|
40
|
+
@eng.register("maybe")
|
|
41
|
+
def maybe(*a, **k) -> PredicateResult:
|
|
42
|
+
return PredicateResult(truth=Truth.UNKNOWN, certified=False, source="maybe")
|
|
43
|
+
ev3 = eng.evaluate({"op": "AND", "children": [
|
|
44
|
+
{"predicate": "type_prefix", "args": ["$type", "VSL-"]},
|
|
45
|
+
{"predicate": "maybe", "args": ["x"]},
|
|
46
|
+
]}, {"type": "VSL-X"})
|
|
47
|
+
assert ev3.is_unknown, "AND con UNKNOWN debe ser UNKNOWN"
|
|
48
|
+
|
|
49
|
+
# certificación: bool simple NO certifica; PredicateResult certified=True sí
|
|
50
|
+
ev4 = eng.evaluate({"predicate": "type_glob", "args": ["$type", "*.vsm"]}, {"type": "x.vsm"})
|
|
51
|
+
assert ev4.certified, "type_glob es evidencia estructural → certified"
|
|
52
|
+
|
|
53
|
+
# tree_home: primero TRUE gana; UNKNOWN → '?' (None), no else silencioso
|
|
54
|
+
t2 = {"op": "OR", "children": [
|
|
55
|
+
{"predicate": "type_prefix", "args": ["$type", "THEORY-VC-"], "home": "s3-control"},
|
|
56
|
+
{"predicate": "type_prefix", "args": ["$type", "THEORY-AP-"], "home": "s4-intelligence"},
|
|
57
|
+
]}
|
|
58
|
+
assert tree_home(t2, "THEORY-VC-01", eng) == "s3-control"
|
|
59
|
+
assert tree_home(t2, "THEORY-AP-01", eng) == "s4-intelligence"
|
|
60
|
+
assert tree_home(t2, "THEORY-DYN-01", eng) is None # no match → '?'
|
|
61
|
+
|
|
62
|
+
# R10: llm_judge opina (TRUE) pero NO certifica → certified=False
|
|
63
|
+
@eng.register("llm_judge")
|
|
64
|
+
def llm_judge(question: str, evidence: str, **kwargs) -> PredicateResult:
|
|
65
|
+
return PredicateResult(
|
|
66
|
+
truth=Truth.TRUE, certified=False,
|
|
67
|
+
evidence=evidence, source="llm:gpt-4",
|
|
68
|
+
metadata={"question": question, "confidence": 0.85},
|
|
69
|
+
)
|
|
70
|
+
ev5 = eng.evaluate({"predicate": "llm_judge", "kwargs": {
|
|
71
|
+
"question": "¿Rompe compatibilidad?", "evidence": "cambio"}}, {})
|
|
72
|
+
assert ev5.is_true and not ev5.certified, "LLM opina pero no certifica (R10)"
|
|
73
|
+
|
|
74
|
+
# trace inverso: AND con llm_judge no certificado → el trace apunta al llm_judge
|
|
75
|
+
tree_diag = {"op": "AND", "children": [
|
|
76
|
+
{"predicate": "type_prefix", "args": ["$type", "VSL-"]},
|
|
77
|
+
{"predicate": "llm_judge", "kwargs": {"question": "¿OK?", "evidence": "cambio"}},
|
|
78
|
+
]}
|
|
79
|
+
diag = eng.diagnose(tree_diag, {"type": "VSL-X"})
|
|
80
|
+
assert len(diag) >= 1, "diagnose debe encontrar el fallo"
|
|
81
|
+
assert any("llm" in t.path[-1] or "llm" in t.source for t in diag), \
|
|
82
|
+
"el trace inverso debe señalar al llm_judge como causa (source=llm:gpt-4)"
|
|
83
|
+
assert all("certified" not in t.reason or t.reason for t in diag)
|
|
84
|
+
|
|
85
|
+
# builder: árbol válido pasa; predicado no registrado → ValueError descriptivo
|
|
86
|
+
builder = SocraticTreeBuilder(eng)
|
|
87
|
+
built = builder.build({"op": "OR", "children": [
|
|
88
|
+
{"predicate": "type_prefix", "args": ["$type", "THEORY-VC-"]},
|
|
89
|
+
]})
|
|
90
|
+
assert eng.evaluate(built, {"type": "THEORY-VC-01"}).is_true
|
|
91
|
+
try:
|
|
92
|
+
builder.build({"op": "AND", "children": [{"predicate": "no_such", "args": []}]})
|
|
93
|
+
raise AssertionError("builder debe rechazar predicado no registrado")
|
|
94
|
+
except ValueError as e:
|
|
95
|
+
assert "no_such" in str(e), "mensaje debe nombrar el predicado"
|
|
96
|
+
try:
|
|
97
|
+
builder.build({"op": "NOT", "children": [True, False]})
|
|
98
|
+
raise AssertionError("builder debe rechazar NOT con 2 hijos")
|
|
99
|
+
except ValueError as e:
|
|
100
|
+
assert "NOT" in str(e), "mensaje debe nombrar el operador"
|
|
101
|
+
|
|
102
|
+
print("✓ socratic_engine selftest OK — trivaluado + certified + explain + diagnose + builder discriminan")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
106
|
+
# CLI EXTERNA: socratic-eval — el contrato del shell único de plugins (fase a,
|
|
107
|
+
# PLUGIN-CLASS-TAXONOMY-v1). Un plugin TS delgado (o un gate write-time) llama
|
|
108
|
+
# a este evaluador con un árbol + contexto JSON; recibe la decisión como JSON.
|
|
109
|
+
# R10: el motor decide con evidencia estructural; el LLM puede proponer, nunca
|
|
110
|
+
# certificar. R9: UNKNOWN → '?' visible en home, nunca else_home silencioso.
|
|
111
|
+
#
|
|
112
|
+
# Uso:
|
|
113
|
+
# python3 scripts/vsl/socratic_engine.py eval-tree <arbol.vsm|arbol.json> \
|
|
114
|
+
# --context '{"type":"VSL-LANG-GATES-v1.0","path":"..."}'
|
|
115
|
+
# → {"truth":"TRUE","certified":true,"home":"vsl-language","unknown":false,
|
|
116
|
+
# "diagnose":[...],"explain":"..."}
|
|
117
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
def _eval_tree_cli(argv: list[str]) -> int:
|
|
120
|
+
if len(argv) < 1:
|
|
121
|
+
print("usage: socratic_engine.py eval-tree <tree.vsm|tree.json> "
|
|
122
|
+
"[--context <json>] [--doc-type <TYPE>]", file=sys.stderr)
|
|
123
|
+
return 2
|
|
124
|
+
tree_path = Path(argv[0])
|
|
125
|
+
if not tree_path.exists():
|
|
126
|
+
print(f"tree not found: {tree_path}", file=sys.stderr)
|
|
127
|
+
return 2
|
|
128
|
+
ctx: dict = {}
|
|
129
|
+
i = 1
|
|
130
|
+
while i < len(argv):
|
|
131
|
+
if argv[i] == "--context" and i + 1 < len(argv):
|
|
132
|
+
try:
|
|
133
|
+
ctx = json.loads(argv[i + 1])
|
|
134
|
+
except json.JSONDecodeError as e:
|
|
135
|
+
print(f"context is not valid JSON: {e}", file=sys.stderr)
|
|
136
|
+
return 2
|
|
137
|
+
i += 2
|
|
138
|
+
elif argv[i] == "--doc-type" and i + 1 < len(argv):
|
|
139
|
+
ctx["type"] = argv[i + 1]
|
|
140
|
+
i += 2
|
|
141
|
+
else:
|
|
142
|
+
i += 1
|
|
143
|
+
# árbol: .json → dict directo; .vsm → parse_socratic_block
|
|
144
|
+
if tree_path.suffix == ".json":
|
|
145
|
+
tree = json.loads(tree_path.read_text(encoding="utf-8"))
|
|
146
|
+
else:
|
|
147
|
+
tree = parse_socratic_block(tree_path.read_text(encoding="utf-8"))
|
|
148
|
+
if tree is None:
|
|
149
|
+
print("no socratic(...) block found in tree file", file=sys.stderr)
|
|
150
|
+
return 2
|
|
151
|
+
eng = SocraticEngine()
|
|
152
|
+
try:
|
|
153
|
+
ev = eng.evaluate(tree, ctx)
|
|
154
|
+
except (ValueError, KeyError, TypeError) as e:
|
|
155
|
+
print(f"evaluation error: {e}", file=sys.stderr)
|
|
156
|
+
return 1
|
|
157
|
+
out: dict = {
|
|
158
|
+
"truth": ev.truth.name if hasattr(ev.truth, "name") else str(ev.truth),
|
|
159
|
+
"certified": ev.certified,
|
|
160
|
+
"unknown": ev.is_unknown,
|
|
161
|
+
"home": tree_home(tree, ctx.get("type", ""), eng, ctx),
|
|
162
|
+
"explain": ev.explain(),
|
|
163
|
+
"diagnose": [t.to_dict() if hasattr(t, "to_dict") else str(t)
|
|
164
|
+
for t in eng.diagnose(tree, ctx)],
|
|
165
|
+
}
|
|
166
|
+
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def main(argv: list[str] | None = None) -> int:
|
|
171
|
+
"""Entry point: `socratic-engine eval-tree <tree> [opts]` o selftest."""
|
|
172
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
173
|
+
if args and args[0] == "eval-tree":
|
|
174
|
+
return _eval_tree_cli(args[1:])
|
|
175
|
+
# selftest rápido (R4.1: el instrumento se auto-verifica)
|
|
176
|
+
_run_selftest()
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if __name__ == "__main__":
|
|
181
|
+
sys.exit(main())
|
|
182
|
+
|