schemarouter 0.2.0a1__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.
- schemarouter/__init__.py +88 -0
- schemarouter/_version.py +8 -0
- schemarouter/adapters/__init__.py +29 -0
- schemarouter/adapters/base.py +71 -0
- schemarouter/adapters/mcp.py +179 -0
- schemarouter/adapters/openapi.py +418 -0
- schemarouter/adapters/optimade.py +656 -0
- schemarouter/adapters/python.py +188 -0
- schemarouter/analyzers/__init__.py +3 -0
- schemarouter/analyzers/model.py +185 -0
- schemarouter/errors.py +50 -0
- schemarouter/executor.py +207 -0
- schemarouter/ingestion.py +348 -0
- schemarouter/integrations/__init__.py +3 -0
- schemarouter/integrations/langchain.py +96 -0
- schemarouter/models.py +154 -0
- schemarouter/planner.py +253 -0
- schemarouter/policy.py +51 -0
- schemarouter/proposals.py +391 -0
- schemarouter/py.typed +0 -0
- schemarouter/registry.py +84 -0
- schemarouter/runs.py +77 -0
- schemarouter/runtime.py +686 -0
- schemarouter/validation.py +96 -0
- schemarouter-0.2.0a1.dist-info/METADATA +297 -0
- schemarouter-0.2.0a1.dist-info/RECORD +28 -0
- schemarouter-0.2.0a1.dist-info/WHEEL +4 -0
- schemarouter-0.2.0a1.dist-info/licenses/LICENSE +21 -0
schemarouter/registry.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
from .errors import RegistrationError
|
|
7
|
+
from .models import EndpointSpec, ToolSpec
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ToolRegistry(Protocol):
|
|
11
|
+
"""Structural contract for pluggable tool registries.
|
|
12
|
+
|
|
13
|
+
Read methods must return detached snapshots (or immutable equivalents) so callers cannot
|
|
14
|
+
mutate registry state without going through a versioned write operation.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def version(self) -> int: ...
|
|
19
|
+
|
|
20
|
+
def register(self, tool: ToolSpec, *, replace: bool = False) -> str: ...
|
|
21
|
+
|
|
22
|
+
def get(self, key: str) -> ToolSpec: ...
|
|
23
|
+
|
|
24
|
+
def tools(self) -> tuple[ToolSpec, ...]: ...
|
|
25
|
+
|
|
26
|
+
def keys(self) -> tuple[str, ...]: ...
|
|
27
|
+
|
|
28
|
+
def endpoint(self, tool_key: str, endpoint_name: str) -> EndpointSpec: ...
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class InMemoryRegistry:
|
|
32
|
+
"""Versioned, collision-safe in-memory tool catalog with snapshot reads."""
|
|
33
|
+
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
self._tools: dict[str, ToolSpec] = {}
|
|
36
|
+
self._version = 0
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def version(self) -> int:
|
|
40
|
+
return self._version
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _snapshot(tool: ToolSpec) -> ToolSpec:
|
|
44
|
+
return tool.model_copy(deep=True)
|
|
45
|
+
|
|
46
|
+
def register(self, tool: ToolSpec, *, replace: bool = False) -> str:
|
|
47
|
+
key = tool.key
|
|
48
|
+
if key in self._tools and not replace:
|
|
49
|
+
raise RegistrationError(f"tool {key!r} is already registered")
|
|
50
|
+
self._tools[key] = self._snapshot(tool)
|
|
51
|
+
self._version += 1
|
|
52
|
+
return key
|
|
53
|
+
|
|
54
|
+
def unregister(self, key: str) -> None:
|
|
55
|
+
if key not in self._tools:
|
|
56
|
+
raise KeyError(key)
|
|
57
|
+
del self._tools[key]
|
|
58
|
+
self._version += 1
|
|
59
|
+
|
|
60
|
+
def get(self, key: str) -> ToolSpec:
|
|
61
|
+
return self._snapshot(self._tools[key])
|
|
62
|
+
|
|
63
|
+
def tools(self) -> tuple[ToolSpec, ...]:
|
|
64
|
+
return tuple(self._snapshot(tool) for tool in self._tools.values())
|
|
65
|
+
|
|
66
|
+
def keys(self) -> tuple[str, ...]:
|
|
67
|
+
return tuple(self._tools)
|
|
68
|
+
|
|
69
|
+
def endpoint(self, tool_key: str, endpoint_name: str) -> EndpointSpec:
|
|
70
|
+
return self.get(tool_key).endpoint(endpoint_name)
|
|
71
|
+
|
|
72
|
+
def update_many(self, tools: Iterable[ToolSpec], *, replace: bool = False) -> None:
|
|
73
|
+
staged = list(tools)
|
|
74
|
+
staged_keys = [tool.key for tool in staged]
|
|
75
|
+
if len(staged_keys) != len(set(staged_keys)):
|
|
76
|
+
raise RegistrationError("duplicate tool keys in batch")
|
|
77
|
+
if not replace:
|
|
78
|
+
collisions = sorted(set(staged_keys) & set(self._tools))
|
|
79
|
+
if collisions:
|
|
80
|
+
raise RegistrationError(f"tools already registered: {', '.join(collisions)}")
|
|
81
|
+
for tool in staged:
|
|
82
|
+
self._tools[tool.key] = self._snapshot(tool)
|
|
83
|
+
if staged:
|
|
84
|
+
self._version += 1
|
schemarouter/runs.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from .models import StrictModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RetryPolicy(StrictModel):
|
|
12
|
+
"""Deterministic retry policy applied at the trusted executor boundary."""
|
|
13
|
+
|
|
14
|
+
max_attempts: int = Field(default=1, ge=1, le=10)
|
|
15
|
+
initial_backoff_seconds: float = Field(default=0.0, ge=0.0, le=60.0)
|
|
16
|
+
backoff_multiplier: float = Field(default=2.0, ge=1.0, le=10.0)
|
|
17
|
+
max_backoff_seconds: float = Field(default=30.0, ge=0.0, le=300.0)
|
|
18
|
+
retry_non_read_only: bool = False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RunConfig(StrictModel):
|
|
22
|
+
"""Per-run metadata and execution controls."""
|
|
23
|
+
|
|
24
|
+
tags: list[str] = Field(default_factory=list)
|
|
25
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
26
|
+
max_concurrency: int = Field(default=8, ge=1, le=128)
|
|
27
|
+
include_payloads: bool = False
|
|
28
|
+
retry: RetryPolicy = Field(default_factory=RetryPolicy)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
RunEventName = Literal[
|
|
32
|
+
"run.start",
|
|
33
|
+
"plan.end",
|
|
34
|
+
"tool.start",
|
|
35
|
+
"tool.end",
|
|
36
|
+
"tool.error",
|
|
37
|
+
"run.end",
|
|
38
|
+
"run.error",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RunEvent(StrictModel):
|
|
43
|
+
"""Typed event envelope emitted by SchemaRouter streaming APIs."""
|
|
44
|
+
|
|
45
|
+
event: RunEventName
|
|
46
|
+
run_id: str
|
|
47
|
+
sequence: int = Field(ge=0)
|
|
48
|
+
timestamp: datetime
|
|
49
|
+
tags: list[str] = Field(default_factory=list)
|
|
50
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
51
|
+
tool: str | None = None
|
|
52
|
+
endpoint: str | None = None
|
|
53
|
+
data: dict[str, Any] = Field(default_factory=dict)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def create(
|
|
57
|
+
cls,
|
|
58
|
+
*,
|
|
59
|
+
event: RunEventName,
|
|
60
|
+
run_id: str,
|
|
61
|
+
sequence: int,
|
|
62
|
+
config: RunConfig,
|
|
63
|
+
tool: str | None = None,
|
|
64
|
+
endpoint: str | None = None,
|
|
65
|
+
data: dict[str, Any] | None = None,
|
|
66
|
+
) -> RunEvent:
|
|
67
|
+
return cls(
|
|
68
|
+
event=event,
|
|
69
|
+
run_id=run_id,
|
|
70
|
+
sequence=sequence,
|
|
71
|
+
timestamp=datetime.now(timezone.utc),
|
|
72
|
+
tags=list(config.tags),
|
|
73
|
+
metadata=dict(config.metadata),
|
|
74
|
+
tool=tool,
|
|
75
|
+
endpoint=endpoint,
|
|
76
|
+
data=dict(data or {}),
|
|
77
|
+
)
|