bylaw-python 0.4.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.
- bylaw_python/__init__.py +114 -0
- bylaw_python/adapters/__init__.py +1 -0
- bylaw_python/adapters/_core.py +58 -0
- bylaw_python/adapters/crewai.py +99 -0
- bylaw_python/adapters/langchain.py +167 -0
- bylaw_python/adapters/llamaindex.py +90 -0
- bylaw_python/cli.py +366 -0
- bylaw_python/client.py +1595 -0
- bylaw_python/config.py +95 -0
- bylaw_python/counterparty.py +145 -0
- bylaw_python/enforce.py +561 -0
- bylaw_python/exceptions.py +104 -0
- bylaw_python/manifest.py +152 -0
- bylaw_python/models.py +330 -0
- bylaw_python/pending.py +128 -0
- bylaw_python/webhook.py +44 -0
- bylaw_python-0.4.0.dist-info/METADATA +227 -0
- bylaw_python-0.4.0.dist-info/RECORD +20 -0
- bylaw_python-0.4.0.dist-info/WHEEL +4 -0
- bylaw_python-0.4.0.dist-info/entry_points.txt +2 -0
bylaw_python/__init__.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Ledgix ALCV — Python SDK
|
|
2
|
+
# Agent-agnostic compliance shim for SOX 404 policy enforcement
|
|
3
|
+
#
|
|
4
|
+
# Recommended usage:
|
|
5
|
+
# import bylaw_python as ledgix
|
|
6
|
+
#
|
|
7
|
+
# ledgix.configure(agent_id="finance-agent")
|
|
8
|
+
#
|
|
9
|
+
# import tools
|
|
10
|
+
#
|
|
11
|
+
# ledgix.configure(agent_id="finance-agent")
|
|
12
|
+
# ledgix.auto_instrument(tools)
|
|
13
|
+
#
|
|
14
|
+
# Explicit API (advanced):
|
|
15
|
+
# from bylaw_python import LedgixClient, vault_enforce, VaultConfig
|
|
16
|
+
#
|
|
17
|
+
# client = LedgixClient()
|
|
18
|
+
#
|
|
19
|
+
# @vault_enforce(client, tool_name="stripe_refund")
|
|
20
|
+
# def process_refund(amount: float, reason: str, **kwargs):
|
|
21
|
+
# token = kwargs.get("_clearance").token
|
|
22
|
+
# ...
|
|
23
|
+
|
|
24
|
+
"""Ledgix ALCV — agent-agnostic compliance shim for SOX 404 enforcement."""
|
|
25
|
+
|
|
26
|
+
from .client import LedgixClient
|
|
27
|
+
from .config import VaultConfig
|
|
28
|
+
from .enforce import (
|
|
29
|
+
VaultContext,
|
|
30
|
+
auto_instrument,
|
|
31
|
+
configure,
|
|
32
|
+
current_clearance,
|
|
33
|
+
current_token,
|
|
34
|
+
enforce,
|
|
35
|
+
tool,
|
|
36
|
+
vault_enforce,
|
|
37
|
+
)
|
|
38
|
+
from .manifest import Manifest, ManifestRule, load_manifest
|
|
39
|
+
from .exceptions import (
|
|
40
|
+
ClearanceDeniedError,
|
|
41
|
+
ManualReviewTimeoutError,
|
|
42
|
+
PolicyRegistrationError,
|
|
43
|
+
LedgixError,
|
|
44
|
+
QueueSaturatedError,
|
|
45
|
+
ReplayDetectedError,
|
|
46
|
+
ReviewPendingError,
|
|
47
|
+
TokenVerificationError,
|
|
48
|
+
VaultConnectionError,
|
|
49
|
+
)
|
|
50
|
+
from .pending import PendingApproval
|
|
51
|
+
from .webhook import verify_webhook
|
|
52
|
+
from .models import (
|
|
53
|
+
ClearanceRequest,
|
|
54
|
+
ClearanceResponse,
|
|
55
|
+
ConsistencyProof,
|
|
56
|
+
InclusionProof,
|
|
57
|
+
LedgerCheckpoint,
|
|
58
|
+
LedgerEntry,
|
|
59
|
+
LedgerKeyVersion,
|
|
60
|
+
LedgerManifest,
|
|
61
|
+
LedgerProofBundle,
|
|
62
|
+
LedgerVerificationResult,
|
|
63
|
+
PolicyRegistration,
|
|
64
|
+
PolicyRegistrationResponse,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
__version__ = "0.4.0"
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
# Core
|
|
71
|
+
"LedgixClient",
|
|
72
|
+
"VaultConfig",
|
|
73
|
+
# Low-code API
|
|
74
|
+
"configure",
|
|
75
|
+
"enforce",
|
|
76
|
+
"current_clearance",
|
|
77
|
+
"current_token",
|
|
78
|
+
# Manifest / auto-instrumentation
|
|
79
|
+
"auto_instrument",
|
|
80
|
+
"tool",
|
|
81
|
+
"load_manifest",
|
|
82
|
+
"Manifest",
|
|
83
|
+
"ManifestRule",
|
|
84
|
+
# Explicit API
|
|
85
|
+
"vault_enforce",
|
|
86
|
+
"VaultContext",
|
|
87
|
+
# Detach-mode / async approvals
|
|
88
|
+
"PendingApproval",
|
|
89
|
+
# Webhook verification
|
|
90
|
+
"verify_webhook",
|
|
91
|
+
# Models
|
|
92
|
+
"ClearanceRequest",
|
|
93
|
+
"ClearanceResponse",
|
|
94
|
+
"ConsistencyProof",
|
|
95
|
+
"InclusionProof",
|
|
96
|
+
"LedgerCheckpoint",
|
|
97
|
+
"LedgerEntry",
|
|
98
|
+
"LedgerKeyVersion",
|
|
99
|
+
"LedgerManifest",
|
|
100
|
+
"LedgerProofBundle",
|
|
101
|
+
"LedgerVerificationResult",
|
|
102
|
+
"PolicyRegistration",
|
|
103
|
+
"PolicyRegistrationResponse",
|
|
104
|
+
# Exceptions
|
|
105
|
+
"LedgixError",
|
|
106
|
+
"ClearanceDeniedError",
|
|
107
|
+
"ManualReviewTimeoutError",
|
|
108
|
+
"QueueSaturatedError",
|
|
109
|
+
"ReviewPendingError",
|
|
110
|
+
"VaultConnectionError",
|
|
111
|
+
"TokenVerificationError",
|
|
112
|
+
"ReplayDetectedError",
|
|
113
|
+
"PolicyRegistrationError",
|
|
114
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Ledgix ALCV — Framework Adapters
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Ledgix ALCV — Adapter Core Helpers
|
|
2
|
+
# Shared scaffolding used by the LangChain, LlamaIndex, and CrewAI adapters.
|
|
3
|
+
# Framework-specific glue (callback handlers, sync vs async, error-translation
|
|
4
|
+
# policy) stays per-adapter — only the genuinely identical pieces live here.
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from ..client import LedgixClient
|
|
11
|
+
from ..enforce import _get_default_client
|
|
12
|
+
from ..models import ClearanceRequest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def resolve_client(client: LedgixClient | None) -> LedgixClient:
|
|
16
|
+
"""Return the explicit client or fall back to the module-level default
|
|
17
|
+
configured via :func:`bylaw_python.configure`.
|
|
18
|
+
"""
|
|
19
|
+
if client is not None:
|
|
20
|
+
return client
|
|
21
|
+
return _get_default_client()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def build_clearance_request(
|
|
25
|
+
*,
|
|
26
|
+
tool_name: str,
|
|
27
|
+
tool_args: dict[str, Any],
|
|
28
|
+
client: LedgixClient,
|
|
29
|
+
policy_id: str | None = None,
|
|
30
|
+
extra_context: dict[str, Any] | None = None,
|
|
31
|
+
data_categories: list[str] | None = None,
|
|
32
|
+
purpose: str | None = None,
|
|
33
|
+
processing_register_ref: str | None = None,
|
|
34
|
+
dataset_ref: str | None = None,
|
|
35
|
+
) -> ClearanceRequest:
|
|
36
|
+
"""Build a ClearanceRequest with adapter-agnostic defaults pulled from
|
|
37
|
+
``client.config``. ``policy_id``, when set, is merged into ``context``
|
|
38
|
+
after ``extra_context`` so it always wins for that key.
|
|
39
|
+
|
|
40
|
+
Phase 2/6 fields (``data_categories``, ``purpose``,
|
|
41
|
+
``processing_register_ref``, ``dataset_ref``) are forwarded as top-level
|
|
42
|
+
fields so the Vault's processing-register / dataset-lineage validators
|
|
43
|
+
can match them.
|
|
44
|
+
"""
|
|
45
|
+
ctx: dict[str, Any] = dict(extra_context or {})
|
|
46
|
+
if policy_id:
|
|
47
|
+
ctx["policy_id"] = policy_id
|
|
48
|
+
return ClearanceRequest(
|
|
49
|
+
tool_name=tool_name,
|
|
50
|
+
tool_args=tool_args,
|
|
51
|
+
agent_id=client.config.agent_id,
|
|
52
|
+
session_id=client.config.session_id,
|
|
53
|
+
context=ctx,
|
|
54
|
+
data_categories=data_categories,
|
|
55
|
+
purpose=purpose,
|
|
56
|
+
processing_register_ref=processing_register_ref,
|
|
57
|
+
dataset_ref=dataset_ref,
|
|
58
|
+
)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Ledgix ALCV — CrewAI Adapter
|
|
2
|
+
# Wraps CrewAI tools with Vault clearance enforcement
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, Type
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from ..client import LedgixClient
|
|
11
|
+
from ..exceptions import ClearanceDeniedError
|
|
12
|
+
from ._core import build_clearance_request, resolve_client
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from crewai.tools import BaseTool as CrewAIBaseTool
|
|
16
|
+
except ImportError as exc:
|
|
17
|
+
raise ImportError(
|
|
18
|
+
"CrewAI adapter requires crewai. "
|
|
19
|
+
"Install with: pip install bylaw-python[crewai]"
|
|
20
|
+
) from exc
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LedgixCrewAITool(CrewAIBaseTool):
|
|
24
|
+
"""Wraps a CrewAI tool with Vault clearance enforcement.
|
|
25
|
+
|
|
26
|
+
Usage with explicit client::
|
|
27
|
+
|
|
28
|
+
from crewai.tools import BaseTool
|
|
29
|
+
from bylaw_python.adapters.crewai import LedgixCrewAITool
|
|
30
|
+
|
|
31
|
+
guarded = LedgixCrewAITool.wrap(client, MyTool())
|
|
32
|
+
|
|
33
|
+
Usage after :func:`bylaw_python.configure`::
|
|
34
|
+
|
|
35
|
+
guarded = LedgixCrewAITool.wrap(MyTool())
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
name: str = ""
|
|
39
|
+
description: str = ""
|
|
40
|
+
_inner_tool: CrewAIBaseTool
|
|
41
|
+
_client: LedgixClient | None
|
|
42
|
+
_policy_id: str | None
|
|
43
|
+
|
|
44
|
+
class Config:
|
|
45
|
+
arbitrary_types_allowed = True
|
|
46
|
+
underscore_attrs_are_private = True
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
inner_tool: CrewAIBaseTool,
|
|
51
|
+
client: LedgixClient | None = None,
|
|
52
|
+
*,
|
|
53
|
+
policy_id: str | None = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
super().__init__(
|
|
56
|
+
name=f"ledgix_{inner_tool.name}",
|
|
57
|
+
description=inner_tool.description,
|
|
58
|
+
)
|
|
59
|
+
self._inner_tool = inner_tool
|
|
60
|
+
self._client = client
|
|
61
|
+
self._policy_id = policy_id
|
|
62
|
+
|
|
63
|
+
def _resolve_client(self) -> LedgixClient:
|
|
64
|
+
return resolve_client(self._client)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def wrap(
|
|
68
|
+
cls,
|
|
69
|
+
client_or_tool: LedgixClient | CrewAIBaseTool,
|
|
70
|
+
tool: CrewAIBaseTool | None = None,
|
|
71
|
+
*,
|
|
72
|
+
policy_id: str | None = None,
|
|
73
|
+
) -> LedgixCrewAITool:
|
|
74
|
+
"""Convenience factory to wrap a CrewAI tool.
|
|
75
|
+
|
|
76
|
+
Supports two call signatures:
|
|
77
|
+
|
|
78
|
+
- ``LedgixCrewAITool.wrap(client, tool, policy_id=...)`` — explicit client
|
|
79
|
+
- ``LedgixCrewAITool.wrap(tool, policy_id=...)`` — uses global client from :func:`bylaw_python.configure`
|
|
80
|
+
"""
|
|
81
|
+
if isinstance(client_or_tool, LedgixClient):
|
|
82
|
+
return cls(inner_tool=tool, client=client_or_tool, policy_id=policy_id) # type: ignore[arg-type]
|
|
83
|
+
return cls(inner_tool=client_or_tool, client=None, policy_id=policy_id)
|
|
84
|
+
|
|
85
|
+
def _run(self, **kwargs: Any) -> Any:
|
|
86
|
+
client = self._resolve_client()
|
|
87
|
+
request = build_clearance_request(
|
|
88
|
+
tool_name=self._inner_tool.name,
|
|
89
|
+
tool_args=kwargs,
|
|
90
|
+
client=client,
|
|
91
|
+
policy_id=self._policy_id,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
client.request_clearance(request)
|
|
96
|
+
except ClearanceDeniedError as exc:
|
|
97
|
+
return f"BLOCKED: Vault denied this action — {exc.reason}"
|
|
98
|
+
|
|
99
|
+
return self._inner_tool._run(**kwargs)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Ledgix ALCV — LangChain Adapter
|
|
2
|
+
# Provides a callback handler and tool wrapper for LangChain integration
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..client import LedgixClient
|
|
9
|
+
from ..exceptions import ClearanceDeniedError
|
|
10
|
+
from ._core import build_clearance_request, resolve_client
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from langchain_core.callbacks import BaseCallbackHandler
|
|
14
|
+
from langchain_core.tools import BaseTool, ToolException
|
|
15
|
+
except ImportError as exc:
|
|
16
|
+
raise ImportError(
|
|
17
|
+
"LangChain adapter requires langchain-core. "
|
|
18
|
+
"Install with: pip install bylaw-python[langchain]"
|
|
19
|
+
) from exc
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LedgixCallbackHandler(BaseCallbackHandler):
|
|
23
|
+
"""LangChain callback handler that intercepts tool calls for Vault clearance.
|
|
24
|
+
|
|
25
|
+
Usage::
|
|
26
|
+
|
|
27
|
+
from bylaw_python.adapters.langchain import LedgixCallbackHandler
|
|
28
|
+
|
|
29
|
+
handler = LedgixCallbackHandler(client)
|
|
30
|
+
agent = create_agent(callbacks=[handler])
|
|
31
|
+
|
|
32
|
+
If :func:`bylaw_python.configure` has been called, *client* may be omitted::
|
|
33
|
+
|
|
34
|
+
handler = LedgixCallbackHandler()
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, client: LedgixClient | None = None, *, policy_id: str | None = None) -> None:
|
|
38
|
+
self._client = client
|
|
39
|
+
self.policy_id = policy_id
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def client(self) -> LedgixClient:
|
|
43
|
+
return resolve_client(self._client)
|
|
44
|
+
|
|
45
|
+
def on_tool_start(
|
|
46
|
+
self,
|
|
47
|
+
serialized: dict[str, Any],
|
|
48
|
+
input_str: str,
|
|
49
|
+
*,
|
|
50
|
+
run_id: Any = None,
|
|
51
|
+
parent_run_id: Any = None,
|
|
52
|
+
tags: list[str] | None = None,
|
|
53
|
+
metadata: dict[str, Any] | None = None,
|
|
54
|
+
inputs: dict[str, Any] | None = None,
|
|
55
|
+
**kwargs: Any,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Intercept tool start and request Vault clearance."""
|
|
58
|
+
tool_name = serialized.get("name", "unknown_tool")
|
|
59
|
+
tool_args = inputs or {"input": input_str}
|
|
60
|
+
|
|
61
|
+
extra_context = {"langchain_metadata": metadata} if metadata else None
|
|
62
|
+
request = build_clearance_request(
|
|
63
|
+
tool_name=tool_name,
|
|
64
|
+
tool_args=tool_args,
|
|
65
|
+
client=self.client,
|
|
66
|
+
policy_id=self.policy_id,
|
|
67
|
+
extra_context=extra_context,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# This will raise ClearanceDeniedError if denied
|
|
71
|
+
self.client.request_clearance(request)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class LedgixTool(BaseTool):
|
|
75
|
+
"""Wraps an existing LangChain tool with Vault clearance enforcement.
|
|
76
|
+
|
|
77
|
+
Usage with explicit client::
|
|
78
|
+
|
|
79
|
+
from langchain_community.tools import SomeTool
|
|
80
|
+
from bylaw_python.adapters.langchain import LedgixTool
|
|
81
|
+
|
|
82
|
+
guarded_tool = LedgixTool.wrap(client, SomeTool(), policy_id="refund-policy")
|
|
83
|
+
|
|
84
|
+
Usage after :func:`bylaw_python.configure`::
|
|
85
|
+
|
|
86
|
+
guarded_tool = LedgixTool.wrap(SomeTool(), policy_id="refund-policy")
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
name: str = ""
|
|
90
|
+
description: str = ""
|
|
91
|
+
_inner_tool: BaseTool
|
|
92
|
+
_client: LedgixClient | None
|
|
93
|
+
_policy_id: str | None
|
|
94
|
+
|
|
95
|
+
class Config:
|
|
96
|
+
arbitrary_types_allowed = True
|
|
97
|
+
underscore_attrs_are_private = True
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
inner_tool: BaseTool,
|
|
102
|
+
client: LedgixClient | None = None,
|
|
103
|
+
*,
|
|
104
|
+
policy_id: str | None = None,
|
|
105
|
+
) -> None:
|
|
106
|
+
super().__init__(
|
|
107
|
+
name=f"ledgix_{inner_tool.name}",
|
|
108
|
+
description=inner_tool.description,
|
|
109
|
+
)
|
|
110
|
+
self._inner_tool = inner_tool
|
|
111
|
+
self._client = client
|
|
112
|
+
self._policy_id = policy_id
|
|
113
|
+
|
|
114
|
+
def _resolve_client(self) -> LedgixClient:
|
|
115
|
+
return resolve_client(self._client)
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def wrap(
|
|
119
|
+
cls,
|
|
120
|
+
client_or_tool: LedgixClient | BaseTool,
|
|
121
|
+
tool: BaseTool | None = None,
|
|
122
|
+
*,
|
|
123
|
+
policy_id: str | None = None,
|
|
124
|
+
) -> LedgixTool:
|
|
125
|
+
"""Convenience factory to wrap a tool.
|
|
126
|
+
|
|
127
|
+
Supports two call signatures:
|
|
128
|
+
|
|
129
|
+
- ``LedgixTool.wrap(client, tool, policy_id=...)`` — explicit client
|
|
130
|
+
- ``LedgixTool.wrap(tool, policy_id=...)`` — uses global client from :func:`bylaw_python.configure`
|
|
131
|
+
"""
|
|
132
|
+
if isinstance(client_or_tool, LedgixClient):
|
|
133
|
+
return cls(inner_tool=tool, client=client_or_tool, policy_id=policy_id) # type: ignore[arg-type]
|
|
134
|
+
# client_or_tool is actually the tool; no explicit client
|
|
135
|
+
return cls(inner_tool=client_or_tool, client=None, policy_id=policy_id)
|
|
136
|
+
|
|
137
|
+
def _run(self, *args: Any, **kwargs: Any) -> Any:
|
|
138
|
+
client = self._resolve_client()
|
|
139
|
+
request = build_clearance_request(
|
|
140
|
+
tool_name=self._inner_tool.name,
|
|
141
|
+
tool_args=kwargs or ({"input": args[0]} if args else {}),
|
|
142
|
+
client=client,
|
|
143
|
+
policy_id=self._policy_id,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
client.request_clearance(request)
|
|
148
|
+
except ClearanceDeniedError as exc:
|
|
149
|
+
raise ToolException(f"Vault denied: {exc.reason}") from exc
|
|
150
|
+
|
|
151
|
+
return self._inner_tool._run(*args, **kwargs)
|
|
152
|
+
|
|
153
|
+
async def _arun(self, *args: Any, **kwargs: Any) -> Any:
|
|
154
|
+
client = self._resolve_client()
|
|
155
|
+
request = build_clearance_request(
|
|
156
|
+
tool_name=self._inner_tool.name,
|
|
157
|
+
tool_args=kwargs or ({"input": args[0]} if args else {}),
|
|
158
|
+
client=client,
|
|
159
|
+
policy_id=self._policy_id,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
await client.arequest_clearance(request)
|
|
164
|
+
except ClearanceDeniedError as exc:
|
|
165
|
+
raise ToolException(f"Vault denied: {exc.reason}") from exc
|
|
166
|
+
|
|
167
|
+
return await self._inner_tool._arun(*args, **kwargs)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Ledgix ALCV — LlamaIndex Adapter
|
|
2
|
+
# Wraps LlamaIndex tools with Vault clearance enforcement
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..client import LedgixClient
|
|
9
|
+
from ._core import build_clearance_request, resolve_client
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from llama_index.core.tools import FunctionTool, ToolMetadata, ToolOutput
|
|
13
|
+
except ImportError as exc:
|
|
14
|
+
raise ImportError(
|
|
15
|
+
"LlamaIndex adapter requires llama-index-core. "
|
|
16
|
+
"Install with: pip install bylaw-python[llamaindex]"
|
|
17
|
+
) from exc
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LedgixToolWrapper:
|
|
21
|
+
"""Wraps a LlamaIndex tool with Vault clearance enforcement.
|
|
22
|
+
|
|
23
|
+
Usage with explicit client::
|
|
24
|
+
|
|
25
|
+
from llama_index.core.tools import FunctionTool
|
|
26
|
+
from bylaw_python.adapters.llamaindex import LedgixToolWrapper
|
|
27
|
+
|
|
28
|
+
tool = FunctionTool.from_defaults(fn=my_tool, name="search")
|
|
29
|
+
guarded = LedgixToolWrapper(client, tool)
|
|
30
|
+
|
|
31
|
+
Usage after :func:`bylaw_python.configure`::
|
|
32
|
+
|
|
33
|
+
guarded = LedgixToolWrapper(tool=tool)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
client: LedgixClient | None = None,
|
|
39
|
+
tool: FunctionTool | None = None,
|
|
40
|
+
*,
|
|
41
|
+
policy_id: str | None = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
self._client = client
|
|
44
|
+
self._inner_tool = tool
|
|
45
|
+
self._policy_id = policy_id
|
|
46
|
+
|
|
47
|
+
# Create the wrapped tool
|
|
48
|
+
self.tool = FunctionTool.from_defaults(
|
|
49
|
+
fn=self._guarded_call,
|
|
50
|
+
name=f"ledgix_{tool.metadata.name}", # type: ignore[union-attr]
|
|
51
|
+
description=tool.metadata.description or "", # type: ignore[union-attr]
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def _resolve_client(self) -> LedgixClient:
|
|
55
|
+
return resolve_client(self._client)
|
|
56
|
+
|
|
57
|
+
def _guarded_call(self, **kwargs: Any) -> Any:
|
|
58
|
+
"""Wrapper that requests clearance before calling the inner tool."""
|
|
59
|
+
client = self._resolve_client()
|
|
60
|
+
request = build_clearance_request(
|
|
61
|
+
tool_name=self._inner_tool.metadata.name, # type: ignore[union-attr]
|
|
62
|
+
tool_args=kwargs,
|
|
63
|
+
client=client,
|
|
64
|
+
policy_id=self._policy_id,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
client.request_clearance(request)
|
|
68
|
+
return self._inner_tool.call(**kwargs) # type: ignore[union-attr]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def wrap_tool(
|
|
72
|
+
client_or_tool: LedgixClient | FunctionTool,
|
|
73
|
+
tool: FunctionTool | None = None,
|
|
74
|
+
*,
|
|
75
|
+
policy_id: str | None = None,
|
|
76
|
+
) -> FunctionTool:
|
|
77
|
+
"""Wrap a LlamaIndex tool with Vault clearance enforcement.
|
|
78
|
+
|
|
79
|
+
Returns the guarded FunctionTool ready for use in an agent.
|
|
80
|
+
|
|
81
|
+
Supports two call signatures:
|
|
82
|
+
|
|
83
|
+
- ``wrap_tool(client, tool, policy_id=...)`` — explicit client
|
|
84
|
+
- ``wrap_tool(tool, policy_id=...)`` — uses global client from :func:`bylaw_python.configure`
|
|
85
|
+
"""
|
|
86
|
+
if isinstance(client_or_tool, LedgixClient):
|
|
87
|
+
wrapper = LedgixToolWrapper(client_or_tool, tool, policy_id=policy_id)
|
|
88
|
+
else:
|
|
89
|
+
wrapper = LedgixToolWrapper(None, client_or_tool, policy_id=policy_id)
|
|
90
|
+
return wrapper.tool
|