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.
@@ -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
+