jit-protocol 1.0.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.
- jit_protocol-1.0.0/PKG-INFO +92 -0
- jit_protocol-1.0.0/README.md +75 -0
- jit_protocol-1.0.0/jit_api/__init__.py +39 -0
- jit_protocol-1.0.0/jit_api/codegen.py +188 -0
- jit_protocol-1.0.0/jit_api/engine.py +200 -0
- jit_protocol-1.0.0/jit_api/fallback_handler.py +51 -0
- jit_protocol-1.0.0/jit_api/needle_engine.py +159 -0
- jit_protocol-1.0.0/jit_api/observer.py +183 -0
- jit_protocol-1.0.0/jit_api/router.py +194 -0
- jit_protocol-1.0.0/jit_api/types.py +69 -0
- jit_protocol-1.0.0/jit_api/typesafe_client.py +75 -0
- jit_protocol-1.0.0/jit_protocol.egg-info/PKG-INFO +92 -0
- jit_protocol-1.0.0/jit_protocol.egg-info/SOURCES.txt +20 -0
- jit_protocol-1.0.0/jit_protocol.egg-info/dependency_links.txt +1 -0
- jit_protocol-1.0.0/jit_protocol.egg-info/requires.txt +12 -0
- jit_protocol-1.0.0/jit_protocol.egg-info/top_level.txt +1 -0
- jit_protocol-1.0.0/pyproject.toml +24 -0
- jit_protocol-1.0.0/setup.cfg +4 -0
- jit_protocol-1.0.0/tests/test_lifecycle.py +91 -0
- jit_protocol-1.0.0/tests/test_needle_engine.py +64 -0
- jit_protocol-1.0.0/tests/test_observer.py +70 -0
- jit_protocol-1.0.0/tests/test_typesafe_client.py +45 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jit-protocol
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Just-In-Time Protocol Synthesis Framework: Dynamic-to-Static API Generation using TypeSafe (Jev) and Needle
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pydantic>=2.0.0
|
|
8
|
+
Requires-Dist: requests>=2.28.0
|
|
9
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
10
|
+
Requires-Dist: cactus-needle>=3.0.0
|
|
11
|
+
Provides-Extra: fastapi
|
|
12
|
+
Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
|
|
13
|
+
Requires-Dist: uvicorn>=0.20.0; extra == "fastapi"
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
16
|
+
Requires-Dist: pytest-asyncio>=0.20.0; extra == "test"
|
|
17
|
+
|
|
18
|
+
# JIT Protocol Synthesis Framework - Python Engine (`jit-api-python`)
|
|
19
|
+
|
|
20
|
+
> **基於 TypeSafe (Jev) 與 Cactus Needle 3.0 的原生 Python 動態至靜態 API 合成引擎**
|
|
21
|
+
> *Pythonic Native Host Engine for Just-In-Time Protocol Synthesis*
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🌟 特色
|
|
26
|
+
|
|
27
|
+
1. **同進程 Cactus Needle 3.0 端側推論**:
|
|
28
|
+
在無 `TYPESAFE_API_KEY` 或離線狀態下,直接調用進程內的 Needle 3 SAN (2-bit, 35MB) 進行毫秒級 Tool Calling 與結構萃取,**0 網路轉發延遲、0 API 費用**。
|
|
29
|
+
2. **極致優雅的裝飾器語法**:
|
|
30
|
+
使用 `@engine.route(...)` 宣告語意意圖,無縫支援 `async def` 與同步 `def` 函式。
|
|
31
|
+
3. **Pydantic v2 動態模型極速路徑**:
|
|
32
|
+
當路由結構在過去 N 次請求中穩定後,自動生成動態 Pydantic v2 BaseModel,Phase 3 靜態驗證僅需 **0.03ms (0ms AI 延遲)**!
|
|
33
|
+
4. **自動版本演進與降級保護**:
|
|
34
|
+
若遇到型態漂移(Schema Drift),自動降級回 Phase 1 並開啟 v2 / v3 版本的觀測與合約重新凍結。
|
|
35
|
+
5. **多語言程式碼生成積木**:
|
|
36
|
+
凍結時同步輸出 TypeScript (Zod)、Golang (.proto / struct) 與 Python (Pydantic / FastAPI)。
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🚀 快速上手 (Quick Start)
|
|
41
|
+
|
|
42
|
+
### 執行環境 (Conda `toby`)
|
|
43
|
+
```bash
|
|
44
|
+
conda activate toby
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 宣告端點與執行
|
|
48
|
+
```python
|
|
49
|
+
import asyncio
|
|
50
|
+
from jit_api import JITEngine
|
|
51
|
+
|
|
52
|
+
engine = JITEngine(stability_threshold=3)
|
|
53
|
+
|
|
54
|
+
@engine.route(
|
|
55
|
+
name="create_invoice",
|
|
56
|
+
description="Issue a customer billing invoice",
|
|
57
|
+
intent="Bill or invoice a customer for items",
|
|
58
|
+
enum_fields={"currency": {"USD": "US Dollar", "EUR": "Euro"}}
|
|
59
|
+
)
|
|
60
|
+
async def create_invoice(payload, ctx):
|
|
61
|
+
return {
|
|
62
|
+
"invoice_id": "INV-100",
|
|
63
|
+
"customer": payload.get("customer"),
|
|
64
|
+
"amount": payload.get("amount"),
|
|
65
|
+
"phase": ctx.phase,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async def main():
|
|
69
|
+
# 支援自然語言或鬆散 JSON
|
|
70
|
+
res = await engine.execute({
|
|
71
|
+
"message": "Please bill Wayne Corp for 500 dollars urgently",
|
|
72
|
+
"customer": "Wayne Corp",
|
|
73
|
+
"amount": 500,
|
|
74
|
+
"currency": "USD"
|
|
75
|
+
})
|
|
76
|
+
print(res.data)
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
asyncio.run(main())
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 🧪 執行測試與演示
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# 執行全套 pytest 測試
|
|
88
|
+
PYTHONPATH=python /home/toby/miniconda3/envs/toby/bin/pytest python/tests/ -v
|
|
89
|
+
|
|
90
|
+
# 執行端到端生命週期演示
|
|
91
|
+
PYTHONPATH=python /home/toby/miniconda3/envs/toby/bin/python python/examples/demo_lifecycle.py
|
|
92
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# JIT Protocol Synthesis Framework - Python Engine (`jit-api-python`)
|
|
2
|
+
|
|
3
|
+
> **基於 TypeSafe (Jev) 與 Cactus Needle 3.0 的原生 Python 動態至靜態 API 合成引擎**
|
|
4
|
+
> *Pythonic Native Host Engine for Just-In-Time Protocol Synthesis*
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 🌟 特色
|
|
9
|
+
|
|
10
|
+
1. **同進程 Cactus Needle 3.0 端側推論**:
|
|
11
|
+
在無 `TYPESAFE_API_KEY` 或離線狀態下,直接調用進程內的 Needle 3 SAN (2-bit, 35MB) 進行毫秒級 Tool Calling 與結構萃取,**0 網路轉發延遲、0 API 費用**。
|
|
12
|
+
2. **極致優雅的裝飾器語法**:
|
|
13
|
+
使用 `@engine.route(...)` 宣告語意意圖,無縫支援 `async def` 與同步 `def` 函式。
|
|
14
|
+
3. **Pydantic v2 動態模型極速路徑**:
|
|
15
|
+
當路由結構在過去 N 次請求中穩定後,自動生成動態 Pydantic v2 BaseModel,Phase 3 靜態驗證僅需 **0.03ms (0ms AI 延遲)**!
|
|
16
|
+
4. **自動版本演進與降級保護**:
|
|
17
|
+
若遇到型態漂移(Schema Drift),自動降級回 Phase 1 並開啟 v2 / v3 版本的觀測與合約重新凍結。
|
|
18
|
+
5. **多語言程式碼生成積木**:
|
|
19
|
+
凍結時同步輸出 TypeScript (Zod)、Golang (.proto / struct) 與 Python (Pydantic / FastAPI)。
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🚀 快速上手 (Quick Start)
|
|
24
|
+
|
|
25
|
+
### 執行環境 (Conda `toby`)
|
|
26
|
+
```bash
|
|
27
|
+
conda activate toby
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 宣告端點與執行
|
|
31
|
+
```python
|
|
32
|
+
import asyncio
|
|
33
|
+
from jit_api import JITEngine
|
|
34
|
+
|
|
35
|
+
engine = JITEngine(stability_threshold=3)
|
|
36
|
+
|
|
37
|
+
@engine.route(
|
|
38
|
+
name="create_invoice",
|
|
39
|
+
description="Issue a customer billing invoice",
|
|
40
|
+
intent="Bill or invoice a customer for items",
|
|
41
|
+
enum_fields={"currency": {"USD": "US Dollar", "EUR": "Euro"}}
|
|
42
|
+
)
|
|
43
|
+
async def create_invoice(payload, ctx):
|
|
44
|
+
return {
|
|
45
|
+
"invoice_id": "INV-100",
|
|
46
|
+
"customer": payload.get("customer"),
|
|
47
|
+
"amount": payload.get("amount"),
|
|
48
|
+
"phase": ctx.phase,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async def main():
|
|
52
|
+
# 支援自然語言或鬆散 JSON
|
|
53
|
+
res = await engine.execute({
|
|
54
|
+
"message": "Please bill Wayne Corp for 500 dollars urgently",
|
|
55
|
+
"customer": "Wayne Corp",
|
|
56
|
+
"amount": 500,
|
|
57
|
+
"currency": "USD"
|
|
58
|
+
})
|
|
59
|
+
print(res.data)
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
asyncio.run(main())
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 🧪 執行測試與演示
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# 執行全套 pytest 測試
|
|
71
|
+
PYTHONPATH=python /home/toby/miniconda3/envs/toby/bin/pytest python/tests/ -v
|
|
72
|
+
|
|
73
|
+
# 執行端到端生命週期演示
|
|
74
|
+
PYTHONPATH=python /home/toby/miniconda3/envs/toby/bin/python python/examples/demo_lifecycle.py
|
|
75
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JIT Protocol Synthesis Framework - Python Host Engine (jit-api-python)
|
|
3
|
+
Dynamic Semantic Negotiation ➔ Static Code Freeze ➔ Fallback
|
|
4
|
+
Powered by TypeSafe (Jev) and Cactus Needle 3.0
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .engine import JITEngine
|
|
8
|
+
from .types import (
|
|
9
|
+
IRField,
|
|
10
|
+
IRSchema,
|
|
11
|
+
JITExecutionResult,
|
|
12
|
+
JITRequestContext,
|
|
13
|
+
LifecyclePhase,
|
|
14
|
+
RouteDefinition,
|
|
15
|
+
StabilityMetrics,
|
|
16
|
+
)
|
|
17
|
+
from .typesafe_client import TypeSafeClient
|
|
18
|
+
from .needle_engine import NeedleEngine
|
|
19
|
+
from .router import TypeSafeRouter
|
|
20
|
+
from .observer import SchemaObserver
|
|
21
|
+
from .fallback_handler import FallbackHandler
|
|
22
|
+
from .codegen import CodegenEngine
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"JITEngine",
|
|
26
|
+
"TypeSafeClient",
|
|
27
|
+
"NeedleEngine",
|
|
28
|
+
"TypeSafeRouter",
|
|
29
|
+
"SchemaObserver",
|
|
30
|
+
"FallbackHandler",
|
|
31
|
+
"CodegenEngine",
|
|
32
|
+
"IRField",
|
|
33
|
+
"IRSchema",
|
|
34
|
+
"JITExecutionResult",
|
|
35
|
+
"JITRequestContext",
|
|
36
|
+
"LifecyclePhase",
|
|
37
|
+
"RouteDefinition",
|
|
38
|
+
"StabilityMetrics",
|
|
39
|
+
]
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
from .types import IRField, IRSchema
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CodegenEngine:
|
|
8
|
+
"""
|
|
9
|
+
Compiles IRSchema into multi-language contracts (TypeScript, Go, Python, IR JSON).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, output_dir: Optional[str] = None):
|
|
13
|
+
self.output_dir = output_dir or os.path.abspath("generated")
|
|
14
|
+
|
|
15
|
+
def _ensure_dirs(self) -> None:
|
|
16
|
+
for sub in ["typescript", "golang", "python", "schemas"]:
|
|
17
|
+
os.makedirs(os.path.join(self.output_dir, sub), exist_ok=True)
|
|
18
|
+
|
|
19
|
+
def generate_python(self, schema: IRSchema) -> str:
|
|
20
|
+
fields_code = []
|
|
21
|
+
for f in schema.fields.values():
|
|
22
|
+
py_t = "str"
|
|
23
|
+
if f.type == "number":
|
|
24
|
+
py_t = "float"
|
|
25
|
+
elif f.type == "boolean":
|
|
26
|
+
py_t = "bool"
|
|
27
|
+
elif f.type == "array":
|
|
28
|
+
item_t = "float" if f.item_type == "number" else ("bool" if f.item_type == "boolean" else "str")
|
|
29
|
+
py_t = f"list[{item_t}]"
|
|
30
|
+
elif f.type == "object":
|
|
31
|
+
py_t = "dict[str, Any]"
|
|
32
|
+
|
|
33
|
+
if not f.required:
|
|
34
|
+
py_t = f"Optional[{py_t}] = None"
|
|
35
|
+
|
|
36
|
+
fields_code.append(f" {f.name}: {py_t}")
|
|
37
|
+
|
|
38
|
+
return f'''"""
|
|
39
|
+
AUTO-GENERATED BY JIT PROTOCOL SYNTHESIS FRAMEWORK (Python)
|
|
40
|
+
Route: {schema.route} (v{schema.version})
|
|
41
|
+
Frozen At: {schema.frozen_at}
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
from typing import Any, Optional
|
|
45
|
+
from pydantic import BaseModel
|
|
46
|
+
from fastapi import APIRouter
|
|
47
|
+
|
|
48
|
+
router = APIRouter(prefix="/{schema.route}", tags=["{schema.route}"])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class {schema.name}(BaseModel):
|
|
52
|
+
{chr(10).join(fields_code)}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@router.post("/", response_model=dict[str, Any])
|
|
56
|
+
async def handle_{schema.route.replace("-", "_")}(payload: {schema.name}):
|
|
57
|
+
return {{
|
|
58
|
+
"status": "success",
|
|
59
|
+
"route": "{schema.route}",
|
|
60
|
+
"version": {schema.version},
|
|
61
|
+
"data": payload.model_dump(),
|
|
62
|
+
}}
|
|
63
|
+
'''
|
|
64
|
+
|
|
65
|
+
def generate_typescript(self, schema: IRSchema) -> str:
|
|
66
|
+
fields_code = []
|
|
67
|
+
for f in schema.fields.values():
|
|
68
|
+
z_t = "z.string()"
|
|
69
|
+
if f.type == "number":
|
|
70
|
+
z_t = "z.number()"
|
|
71
|
+
elif f.type == "boolean":
|
|
72
|
+
z_t = "z.boolean()"
|
|
73
|
+
elif f.type == "array":
|
|
74
|
+
item_z = "z.number()" if f.item_type == "number" else ("z.boolean()" if f.item_type == "boolean" else "z.string()")
|
|
75
|
+
z_t = f"z.array({item_z})"
|
|
76
|
+
elif f.type == "object":
|
|
77
|
+
z_t = "z.record(z.unknown())"
|
|
78
|
+
|
|
79
|
+
if not f.required:
|
|
80
|
+
z_t += ".optional()"
|
|
81
|
+
fields_code.append(f" {f.name}: {z_t},")
|
|
82
|
+
|
|
83
|
+
return f'''/**
|
|
84
|
+
* AUTO-GENERATED BY JIT PROTOCOL SYNTHESIS FRAMEWORK (Python Host)
|
|
85
|
+
* Route: {schema.route} (v{schema.version})
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
import {{ z }} from 'zod';
|
|
89
|
+
|
|
90
|
+
export const {schema.name}Schema = z.object({{
|
|
91
|
+
{chr(10).join(fields_code)}
|
|
92
|
+
}});
|
|
93
|
+
|
|
94
|
+
export type {schema.name} = z.infer<typeof {schema.name}Schema>;
|
|
95
|
+
'''
|
|
96
|
+
|
|
97
|
+
def generate_golang(self, schema: IRSchema) -> Dict[str, str]:
|
|
98
|
+
# 1. Proto3
|
|
99
|
+
proto_fields = []
|
|
100
|
+
for idx, f in enumerate(schema.fields.values(), 1):
|
|
101
|
+
pr_t = "string"
|
|
102
|
+
if f.type == "number":
|
|
103
|
+
pr_t = "double"
|
|
104
|
+
elif f.type == "boolean":
|
|
105
|
+
pr_t = "bool"
|
|
106
|
+
elif f.type == "array":
|
|
107
|
+
pr_t = f"repeated {'double' if f.item_type == 'number' else 'string'}"
|
|
108
|
+
proto_fields.append(f" {pr_t} {f.name} = {idx};")
|
|
109
|
+
|
|
110
|
+
proto = f'''// AUTO-GENERATED BY JIT PROTOCOL SYNTHESIS FRAMEWORK
|
|
111
|
+
syntax = "proto3";
|
|
112
|
+
|
|
113
|
+
package jit.v{schema.version};
|
|
114
|
+
|
|
115
|
+
option go_package = "jit/gen/v{schema.version};jitv{schema.version}";
|
|
116
|
+
|
|
117
|
+
message {schema.name} {{
|
|
118
|
+
{chr(10).join(proto_fields)}
|
|
119
|
+
}}
|
|
120
|
+
'''
|
|
121
|
+
# 2. Go Struct
|
|
122
|
+
go_fields = []
|
|
123
|
+
for f in schema.fields.values():
|
|
124
|
+
field_name = "".join(part.capitalize() for part in f.name.split("_"))
|
|
125
|
+
go_t = "string"
|
|
126
|
+
if f.type == "number":
|
|
127
|
+
go_t = "float64"
|
|
128
|
+
elif f.type == "boolean":
|
|
129
|
+
go_t = "bool"
|
|
130
|
+
elif f.type == "array":
|
|
131
|
+
go_t = "[]float64" if f.item_type == "number" else "[]string"
|
|
132
|
+
elif f.type == "object":
|
|
133
|
+
go_t = "map[string]interface{}"
|
|
134
|
+
go_fields.append(f'\t{field_name} {go_t} `json:"{f.name}"`')
|
|
135
|
+
|
|
136
|
+
go_struct = f'''// AUTO-GENERATED BY JIT PROTOCOL SYNTHESIS FRAMEWORK
|
|
137
|
+
package models
|
|
138
|
+
|
|
139
|
+
type {schema.name} struct {{
|
|
140
|
+
{chr(10).join(go_fields)}
|
|
141
|
+
}}
|
|
142
|
+
'''
|
|
143
|
+
return {"proto": proto, "golang": go_struct}
|
|
144
|
+
|
|
145
|
+
def compile(self, schema: IRSchema) -> Dict[str, Any]:
|
|
146
|
+
self._ensure_dirs()
|
|
147
|
+
base = schema.route.replace("-", "_")
|
|
148
|
+
|
|
149
|
+
py_code = self.generate_python(schema)
|
|
150
|
+
py_path = os.path.join(self.output_dir, "python", f"{base}.py")
|
|
151
|
+
with open(py_path, "w", encoding="utf-8") as f:
|
|
152
|
+
f.write(py_code)
|
|
153
|
+
|
|
154
|
+
ts_code = self.generate_typescript(schema)
|
|
155
|
+
ts_path = os.path.join(self.output_dir, "typescript", f"{base}.ts")
|
|
156
|
+
with open(ts_path, "w", encoding="utf-8") as f:
|
|
157
|
+
f.write(ts_code)
|
|
158
|
+
|
|
159
|
+
go_res = self.generate_golang(schema)
|
|
160
|
+
proto_path = os.path.join(self.output_dir, "golang", f"{base}.proto")
|
|
161
|
+
with open(proto_path, "w", encoding="utf-8") as f:
|
|
162
|
+
f.write(go_res["proto"])
|
|
163
|
+
|
|
164
|
+
go_path = os.path.join(self.output_dir, "golang", f"{base}.go")
|
|
165
|
+
with open(go_path, "w", encoding="utf-8") as f:
|
|
166
|
+
f.write(go_res["golang"])
|
|
167
|
+
|
|
168
|
+
ir_dict = {
|
|
169
|
+
"name": schema.name,
|
|
170
|
+
"version": schema.version,
|
|
171
|
+
"route": schema.route,
|
|
172
|
+
"fields": {k: v.__dict__ for k, v in schema.fields.items()},
|
|
173
|
+
"frozen_at": schema.frozen_at,
|
|
174
|
+
}
|
|
175
|
+
ir_path = os.path.join(self.output_dir, "schemas", f"{base}.json")
|
|
176
|
+
with open(ir_path, "w", encoding="utf-8") as f:
|
|
177
|
+
json.dump(ir_dict, f, indent=2)
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
"schema": schema,
|
|
181
|
+
"files": {
|
|
182
|
+
"python": py_path,
|
|
183
|
+
"typescript": ts_path,
|
|
184
|
+
"proto": proto_path,
|
|
185
|
+
"golang": go_path,
|
|
186
|
+
"ir_json": ir_path,
|
|
187
|
+
},
|
|
188
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import time
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional, Type, Union
|
|
4
|
+
from pydantic import BaseModel, ValidationError, create_model
|
|
5
|
+
|
|
6
|
+
from .codegen import CodegenEngine
|
|
7
|
+
from .fallback_handler import FallbackHandler
|
|
8
|
+
from .needle_engine import NeedleEngine
|
|
9
|
+
from .observer import SchemaObserver
|
|
10
|
+
from .router import TypeSafeRouter
|
|
11
|
+
from .types import (
|
|
12
|
+
IRSchema,
|
|
13
|
+
JITExecutionResult,
|
|
14
|
+
JITRequestContext,
|
|
15
|
+
RouteDefinition,
|
|
16
|
+
RouteHandler,
|
|
17
|
+
)
|
|
18
|
+
from .typesafe_client import TypeSafeClient
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class JITEngine:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
client: Optional[TypeSafeClient] = None,
|
|
25
|
+
needle_engine: Optional[NeedleEngine] = None,
|
|
26
|
+
stability_threshold: int = 5,
|
|
27
|
+
confidence_threshold: float = 0.85,
|
|
28
|
+
fallback_to_needle: bool = True,
|
|
29
|
+
force_needle: bool = False,
|
|
30
|
+
codegen_output_dir: Optional[str] = None,
|
|
31
|
+
on_freeze: Optional[Callable[[Dict[str, Any]], None]] = None,
|
|
32
|
+
on_drift: Optional[Callable[[str, str], None]] = None,
|
|
33
|
+
):
|
|
34
|
+
self.router = TypeSafeRouter(
|
|
35
|
+
client=client,
|
|
36
|
+
needle_engine=needle_engine,
|
|
37
|
+
fallback_to_needle=fallback_to_needle,
|
|
38
|
+
force_needle=force_needle,
|
|
39
|
+
)
|
|
40
|
+
self.codegen = CodegenEngine(codegen_output_dir)
|
|
41
|
+
self.fast_path_validators: Dict[str, Type[BaseModel]] = {}
|
|
42
|
+
self.on_freeze_callback = on_freeze
|
|
43
|
+
self.on_drift_callback = on_drift
|
|
44
|
+
|
|
45
|
+
async def _internal_on_freeze(schema: IRSchema):
|
|
46
|
+
# 1. Compile static files across TS, Go, Python, IR
|
|
47
|
+
codegen_res = self.codegen.compile(schema)
|
|
48
|
+
|
|
49
|
+
# 2. Build dynamic in-memory Pydantic v2 validator for Phase 3
|
|
50
|
+
self._mount_fast_path_validator(schema)
|
|
51
|
+
|
|
52
|
+
if self.on_freeze_callback:
|
|
53
|
+
if inspect.iscoroutinefunction(self.on_freeze_callback):
|
|
54
|
+
await self.on_freeze_callback(codegen_res)
|
|
55
|
+
else:
|
|
56
|
+
self.on_freeze_callback(codegen_res)
|
|
57
|
+
|
|
58
|
+
def _internal_on_drift(route: str, error: str, payload: Dict[str, Any]):
|
|
59
|
+
# Remove fast-path validator to re-enter dynamic Phase 1 mode
|
|
60
|
+
self.fast_path_validators.pop(route, None)
|
|
61
|
+
if self.on_drift_callback:
|
|
62
|
+
self.on_drift_callback(route, error)
|
|
63
|
+
|
|
64
|
+
self.observer = SchemaObserver(
|
|
65
|
+
stability_threshold=stability_threshold,
|
|
66
|
+
confidence_threshold=confidence_threshold,
|
|
67
|
+
on_freeze=_internal_on_freeze,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
self.fallback = FallbackHandler(
|
|
71
|
+
router=self.router,
|
|
72
|
+
observer=self.observer,
|
|
73
|
+
on_drift_detected=_internal_on_drift,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def register(self, route_def: RouteDefinition) -> "JITEngine":
|
|
77
|
+
self.router.register(route_def)
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
def route(
|
|
81
|
+
self,
|
|
82
|
+
name: str,
|
|
83
|
+
description: str,
|
|
84
|
+
intent: str,
|
|
85
|
+
enum_fields: Optional[Dict[str, Dict[str, str]]] = None,
|
|
86
|
+
) -> Callable[[RouteHandler], RouteHandler]:
|
|
87
|
+
"""
|
|
88
|
+
Pythonic decorator to register a route handler:
|
|
89
|
+
@engine.route(name="create_invoice", description="...", intent="...")
|
|
90
|
+
async def create_invoice(payload, ctx):
|
|
91
|
+
return {"status": "ok"}
|
|
92
|
+
"""
|
|
93
|
+
return self.router.route(
|
|
94
|
+
name=name,
|
|
95
|
+
description=description,
|
|
96
|
+
intent=intent,
|
|
97
|
+
enum_fields=enum_fields,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def _mount_fast_path_validator(self, schema: IRSchema) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Dynamically create high-speed Pydantic v2 model for 0ms Phase 3 execution
|
|
103
|
+
"""
|
|
104
|
+
field_definitions: Dict[str, Any] = {}
|
|
105
|
+
|
|
106
|
+
for name, field_meta in schema.fields.items():
|
|
107
|
+
py_type = str
|
|
108
|
+
if field_meta.type == "number":
|
|
109
|
+
py_type = float
|
|
110
|
+
elif field_meta.type == "boolean":
|
|
111
|
+
py_type = bool
|
|
112
|
+
elif field_meta.type == "array":
|
|
113
|
+
item_t = (
|
|
114
|
+
float
|
|
115
|
+
if field_meta.item_type == "number"
|
|
116
|
+
else (bool if field_meta.item_type == "boolean" else str)
|
|
117
|
+
)
|
|
118
|
+
py_type = List[item_t]
|
|
119
|
+
elif field_meta.type == "object":
|
|
120
|
+
py_type = Dict[str, Any]
|
|
121
|
+
|
|
122
|
+
default_val = ... if field_meta.required else None
|
|
123
|
+
field_definitions[name] = (py_type, default_val)
|
|
124
|
+
|
|
125
|
+
model = create_model(f"{schema.name}FastValidator", **field_definitions)
|
|
126
|
+
self.fast_path_validators[schema.route] = model
|
|
127
|
+
|
|
128
|
+
async def execute(
|
|
129
|
+
self, payload: Dict[str, Any], explicit_route: Optional[str] = None
|
|
130
|
+
) -> JITExecutionResult:
|
|
131
|
+
start_time = time.time()
|
|
132
|
+
target_route = explicit_route or (
|
|
133
|
+
payload.get("route") if isinstance(payload.get("route"), str) else None
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
is_target_frozen = target_route and self.observer.is_frozen(target_route)
|
|
137
|
+
|
|
138
|
+
# -------------------------------------------------------------
|
|
139
|
+
# Phase 3: Static Fast-Path (Pydantic v2, 0ms AI Latency)
|
|
140
|
+
# -------------------------------------------------------------
|
|
141
|
+
if is_target_frozen and target_route:
|
|
142
|
+
validator = self.fast_path_validators.get(target_route)
|
|
143
|
+
route_def = self.router.get_route(target_route)
|
|
144
|
+
|
|
145
|
+
if validator and route_def:
|
|
146
|
+
try:
|
|
147
|
+
# Validate natively in-memory via Pydantic v2 C-extension
|
|
148
|
+
validated = validator.model_validate(payload)
|
|
149
|
+
clean_data = validated.model_dump()
|
|
150
|
+
|
|
151
|
+
exec_time_ms = round((time.time() - start_time) * 1000, 2)
|
|
152
|
+
ctx = JITRequestContext(
|
|
153
|
+
route=target_route,
|
|
154
|
+
phase="phase3_frozen",
|
|
155
|
+
execution_time_ms=exec_time_ms,
|
|
156
|
+
ai_latency_ms=0.0, # 0ms AI latency!
|
|
157
|
+
intent_confidence=1.0,
|
|
158
|
+
is_fallback=False,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
data = await self.router._invoke_handler(
|
|
162
|
+
route_def.handler, clean_data, ctx
|
|
163
|
+
)
|
|
164
|
+
return JITExecutionResult(success=True, data=data, context=ctx)
|
|
165
|
+
except ValidationError as val_err:
|
|
166
|
+
# Schema Drift detected! Downgrade to Phase 1 & re-arm Phase 2 for v2
|
|
167
|
+
fallback_res = await self.fallback.handle_fallback(
|
|
168
|
+
target_route, str(val_err), payload
|
|
169
|
+
)
|
|
170
|
+
return JITExecutionResult(
|
|
171
|
+
success=True,
|
|
172
|
+
data=fallback_res["result"],
|
|
173
|
+
context=fallback_res["context"],
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# -------------------------------------------------------------
|
|
177
|
+
# Phase 1: Dynamic Semantic Routing (TypeSafe Jev or Needle)
|
|
178
|
+
# -------------------------------------------------------------
|
|
179
|
+
res = await self.router.handle(payload, preferred_route=target_route)
|
|
180
|
+
|
|
181
|
+
# Phase 2: Observation & Stability Tracking
|
|
182
|
+
obs = await self.observer.observe(
|
|
183
|
+
res["route"],
|
|
184
|
+
res["normalized_payload"],
|
|
185
|
+
res["context"].intent_confidence,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
res["context"].phase = "phase3_frozen" if obs["stable"] else "phase1_dynamic"
|
|
189
|
+
|
|
190
|
+
return JITExecutionResult(
|
|
191
|
+
success=True, data=res["result"], context=res["context"]
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def get_route_status(self, route: str) -> Dict[str, Any]:
|
|
195
|
+
return {
|
|
196
|
+
"route": route,
|
|
197
|
+
"is_frozen": self.observer.is_frozen(route),
|
|
198
|
+
"metrics": self.observer.get_metrics(route),
|
|
199
|
+
"frozen_schema": self.observer.get_frozen_schema(route),
|
|
200
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Any, Callable, Dict, Optional
|
|
3
|
+
from .observer import SchemaObserver
|
|
4
|
+
from .router import TypeSafeRouter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FallbackHandler:
|
|
8
|
+
"""
|
|
9
|
+
Handles Phase 3 Fast-Path validation errors (Schema Drift)
|
|
10
|
+
by downgrading the request back to Phase 1 dynamic semantic resolution
|
|
11
|
+
and re-arming Phase 2 observation for the next contract version (v2, v3...).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
router: TypeSafeRouter,
|
|
17
|
+
observer: SchemaObserver,
|
|
18
|
+
on_drift_detected: Optional[Callable[[str, str, Dict[str, Any]], None]] = None,
|
|
19
|
+
):
|
|
20
|
+
self.router = router
|
|
21
|
+
self.observer = observer
|
|
22
|
+
self.on_drift_detected = on_drift_detected or self._default_log_drift
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def _default_log_drift(route: str, error: str, payload: Dict[str, Any]) -> None:
|
|
26
|
+
sys.stderr.write(
|
|
27
|
+
f"[FallbackHandler] ⚠️ Schema drift detected on '{route}': {error}. Downgrading to Phase 1.\n"
|
|
28
|
+
)
|
|
29
|
+
sys.stderr.flush()
|
|
30
|
+
|
|
31
|
+
async def handle_fallback(
|
|
32
|
+
self, route: str, validation_error: str, raw_payload: Dict[str, Any]
|
|
33
|
+
) -> Dict[str, Any]:
|
|
34
|
+
# 1. Notify drift
|
|
35
|
+
self.on_drift_detected(route, validation_error, raw_payload)
|
|
36
|
+
|
|
37
|
+
# 2. Reset observer for this route to initiate v2 observation
|
|
38
|
+
self.observer.reset_route(route)
|
|
39
|
+
|
|
40
|
+
# 3. Reroute through Phase 1 dynamic semantic router
|
|
41
|
+
res = await self.router.handle(raw_payload, preferred_route=route)
|
|
42
|
+
|
|
43
|
+
# 4. Mark context as fallback
|
|
44
|
+
res["context"].is_fallback = True
|
|
45
|
+
|
|
46
|
+
# 5. Record drifted payload as sample 1 of the new version
|
|
47
|
+
await self.observer.observe(
|
|
48
|
+
route, res["normalized_payload"], res["context"].intent_confidence
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return res
|