vsl-langgraph 0.1.2__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,9 @@
1
+ from .adapter import LangGraphAdapter
2
+ from .integration import DEFAULT_DENIAL_KEY, gated_node, route_on_denial
3
+
4
+ __all__ = [
5
+ "LangGraphAdapter",
6
+ "gated_node",
7
+ "route_on_denial",
8
+ "DEFAULT_DENIAL_KEY",
9
+ ]
@@ -0,0 +1,163 @@
1
+ """LangGraphAdapter: a VSLAdapter conformant with vsl-core's conformance
2
+ contract, intended for use inside LangGraph nodes.
3
+
4
+ LangGraph has no dedicated guardrail/interception type -- a StateGraph node
5
+ is just a plain sync/async callable, and conditional edges are plain router
6
+ functions evaluated after a node returns. There is therefore nothing
7
+ LangGraph-specific to compile a PreNode/Invariant *into*: the compiled gate
8
+ is the same framework-agnostic async callable vsl-core's own
9
+ PlainPythonReferenceAdapter produces, called directly inside a node body
10
+ before the side effect it guards. What this package adds on top (see
11
+ `integration.py`) is the wiring convention for calling that gate from a
12
+ node and routing around a denial with `add_conditional_edges`, not a
13
+ different gate-compilation strategy.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import inspect
19
+ import warnings
20
+ from typing import Any, Callable
21
+
22
+ from vsl_core.constructs import Fallback, Invariant, PreNode
23
+ from vsl_core.conformance.protocol import CompiledGate
24
+ from vsl_core.exceptions import AutomationDeniedException, InvariantViolation
25
+ from vsl_core.metrics import F2Modification
26
+
27
+ _DEFAULT_FALLBACK = Fallback()
28
+
29
+ # Source-text markers for hosted, black-box model APIs. A gate whose
30
+ # monitor/rule reaches one of these cannot honestly claim F2Modification.FULL
31
+ # -- FULL requires direct access to the energy function itself (logits,
32
+ # weights, activations), which a hosted API call structurally cannot expose.
33
+ # The honest ceiling for a hosted-API-backed check is PARTIAL (MEDIUM), per
34
+ # vsl-core's own README example. This is a heuristic, not a proof: it scans
35
+ # source text -- the monitor/rule's OWN body (inspect.getsource(fn), not its
36
+ # containing module's entire source; see _reaches_hosted_api's docstring for
37
+ # why that distinction matters), plus one level of indirection through
38
+ # whatever names it directly calls -- catching the common "the actual API
39
+ # call lives in a helper module" shape, e.g. a monitor in agent.py calling
40
+ # judge_confidence() from a separate providers.py that imports anthropic --
41
+ # for these markers. It can still miss deeper indirection (two or more
42
+ # call-frames away, a callable reached only via a dict/attribute lookup,
43
+ # dynamic imports) and cannot confirm a genuine FULL claim is correct either
44
+ # -- it only catches the one concrete, previously-observed failure mode of
45
+ # claiming FULL while calling a hosted API.
46
+ _HOSTED_API_MARKERS = (
47
+ "openai",
48
+ "anthropic",
49
+ "google.genai",
50
+ "google.generativeai",
51
+ "cohere",
52
+ "mistralai",
53
+ )
54
+
55
+
56
+ def _reaches_hosted_api(fn: Callable[..., Any], _seen: set | None = None) -> bool:
57
+ """Scoped to fn's OWN source (inspect.getsource(fn)), not its containing
58
+ module's entire source. Scanning the whole module was a real false-
59
+ positive bug found while building a companion prototype: an unrelated
60
+ function's comment or docstring elsewhere in the same file could
61
+ trigger a match that had nothing to do with fn itself. Function-level
62
+ scoping fixes that without requiring every monitor/rule to live in its
63
+ own file -- real hosted-API calls are actual code inside the relevant
64
+ function (or a helper it calls, covered by the indirection walk below),
65
+ not incidental mentions elsewhere in the file.
66
+ """
67
+ if _seen is None:
68
+ _seen = set()
69
+ if fn in _seen:
70
+ return False
71
+ _seen.add(fn)
72
+
73
+ if inspect.getmodule(fn) is None:
74
+ return False
75
+ try:
76
+ source = inspect.getsource(fn)
77
+ except (OSError, TypeError):
78
+ return False
79
+ # Case-insensitive: real code capitalizes SDK class names (AsyncAnthropic,
80
+ # AsyncOpenAI), which a case-sensitive check against lowercase "anthropic"/
81
+ # "openai" markers would miss -- found and fixed after function-scoping
82
+ # (above) removed an accidental crutch: the old whole-module scan happened
83
+ # to also catch the lowercase `from anthropic import ...` line elsewhere
84
+ # in the file, masking this case bug until scoping narrowed to fn itself.
85
+ source_lower = source.lower()
86
+ if any(marker in source_lower for marker in _HOSTED_API_MARKERS):
87
+ return True
88
+
89
+ # One level of indirection: follow names this function's body directly
90
+ # references, in case the real hosted-API call lives in a helper it
91
+ # calls rather than in its own module.
92
+ code = getattr(fn, "__code__", None)
93
+ globalns = getattr(fn, "__globals__", None)
94
+ if code is None or globalns is None:
95
+ return False
96
+ for referenced_name in code.co_names:
97
+ candidate = globalns.get(referenced_name)
98
+ if callable(candidate) and candidate is not fn:
99
+ if _reaches_hosted_api(candidate, _seen):
100
+ return True
101
+ return False
102
+
103
+
104
+ def _warn_if_full_claim_reaches_hosted_api(name: str, assurance_basis: Any, fn: Callable[..., Any]) -> None:
105
+ if assurance_basis.f2_modification != F2Modification.FULL:
106
+ return
107
+ if not _reaches_hosted_api(fn):
108
+ return
109
+ warnings.warn(
110
+ f"'{name}' claims F2Modification.FULL, but its monitor/rule appears to "
111
+ "call a hosted model API (openai/anthropic/google.genai/cohere/mistralai "
112
+ "detected in its module's source). A hosted, black-box API call cannot "
113
+ "directly modify the energy function -- FULL is not achievable this way. "
114
+ "The honest ceiling is F2Modification.PARTIAL (MEDIUM assurance), per "
115
+ "vsl-core's own README F1/F2 example. This is a heuristic check, not "
116
+ "proof -- it can miss indirect calls, and passing it does not confirm "
117
+ "FULL is correct either.",
118
+ stacklevel=3,
119
+ )
120
+
121
+
122
+ class LangGraphAdapter:
123
+ def compile_pre_node(self, pre_node: PreNode) -> CompiledGate:
124
+ if pre_node.fallback != _DEFAULT_FALLBACK:
125
+ warnings.warn(
126
+ f"PreNode '{pre_node.name}' sets a non-default Fallback "
127
+ f"({pre_node.fallback!r}), but gated_node does not consume "
128
+ "Fallback -- a denial raises once, immediately, with no "
129
+ "retry loop. See docs/dos-and-donts.md if you need retry "
130
+ "behavior; you must implement it yourself around the "
131
+ "compiled gate.",
132
+ stacklevel=2,
133
+ )
134
+ _warn_if_full_claim_reaches_hosted_api(f"PreNode '{pre_node.name}'", pre_node.assurance_basis, pre_node.monitor)
135
+
136
+ async def gate(candidate_input: Any) -> None:
137
+ estimate = await pre_node.monitor(candidate_input)
138
+ if not estimate.sufficient(pre_node.gamma_threshold):
139
+ raise AutomationDeniedException(
140
+ reason=f"Pre-node '{pre_node.name}' insufficient Gamma",
141
+ identity_key="vsl-langgraph",
142
+ )
143
+
144
+ return gate
145
+
146
+ def compile_invariant(self, invariant: Invariant) -> CompiledGate:
147
+ _warn_if_full_claim_reaches_hosted_api(f"Invariant '{invariant.name}'", invariant.assurance_basis, invariant.rule)
148
+
149
+ async def gate(candidate_input: Any) -> None:
150
+ holds = await invariant.rule(candidate_input)
151
+ if not holds:
152
+ reason = f"Invariant '{invariant.name}' violated"
153
+ terminal_state_name = invariant.on_violation.name if invariant.on_violation is not None else None
154
+ if terminal_state_name is not None:
155
+ reason += f" -- entering terminal state '{terminal_state_name}'"
156
+ raise InvariantViolation(
157
+ reason=reason,
158
+ identity_key="vsl-langgraph",
159
+ invariant_name=invariant.name,
160
+ terminal_state_name=terminal_state_name,
161
+ )
162
+
163
+ return gate
@@ -0,0 +1,81 @@
1
+ """Wiring helpers for calling a compiled VSL gate from inside a LangGraph
2
+ node and routing around a denial with `add_conditional_edges`.
3
+
4
+ vsl-core does not intercept anything on its own (see
5
+ vsl-core/docs/building-with-vsl-core.md, "know what's automatic and what
6
+ you write yourself"): a compiled gate only ever raises or returns, on
7
+ demand, when called. `gated_node` is that call site, made reusable for a
8
+ LangGraph StateGraph -- it calls the gate immediately before the wrapped
9
+ node would run (preserving F1 pre-commitment: the gate completes strictly
10
+ before the guarded node's side effect), and on denial returns a state
11
+ update instead of letting the exception propagate, so a conditional edge
12
+ can route to a blocked/terminal node in the normal LangGraph way rather
13
+ than crashing the graph run.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import inspect
19
+ from typing import Any, Awaitable, Callable, Mapping
20
+
21
+ from vsl_core.conformance.protocol import CompiledGate
22
+ from vsl_core.exceptions import AutomationDeniedException
23
+
24
+ StateNode = Callable[[Mapping[str, Any]], Any]
25
+
26
+ DEFAULT_DENIAL_KEY = "vsl_denial"
27
+
28
+
29
+ def gated_node(
30
+ gate: CompiledGate,
31
+ node: StateNode,
32
+ *,
33
+ candidate_input_fn: Callable[[Mapping[str, Any]], Any] | None = None,
34
+ denial_key: str = DEFAULT_DENIAL_KEY,
35
+ ) -> Callable[[Mapping[str, Any]], Awaitable[dict[str, Any]]]:
36
+ """Wrap `node` so `gate` is checked immediately before it runs.
37
+
38
+ `candidate_input_fn` extracts whatever the gate should evaluate from
39
+ graph state; defaults to passing the whole state through unchanged.
40
+
41
+ On denial, `node` is never called and the returned state update sets
42
+ `denial_key` to the caught exception -- pair with `route_on_denial`
43
+ (or your own router reading `denial_key`) in `add_conditional_edges` to
44
+ send the run to a blocked/terminal node.
45
+
46
+ Does not consume `PreNode.fallback` -- a denial raises once,
47
+ immediately, with no retry loop, regardless of `fallback.max_retries`.
48
+ `LangGraphAdapter.compile_pre_node` warns if a non-default `Fallback`
49
+ is set. See docs/dos-and-donts.md.
50
+ """
51
+ extract = candidate_input_fn or (lambda state: state)
52
+
53
+ async def wrapped(state: Mapping[str, Any]) -> dict[str, Any]:
54
+ try:
55
+ await gate(extract(state))
56
+ except AutomationDeniedException as exc:
57
+ return {denial_key: exc}
58
+
59
+ result = node(state)
60
+ if inspect.isawaitable(result):
61
+ result = await result
62
+ return dict(result) if result else {}
63
+
64
+ return wrapped
65
+
66
+
67
+ def route_on_denial(
68
+ blocked_node: str,
69
+ allowed_node: str,
70
+ *,
71
+ denial_key: str = DEFAULT_DENIAL_KEY,
72
+ ) -> Callable[[Mapping[str, Any]], str]:
73
+ """Build a router for `add_conditional_edges` matching `gated_node`'s
74
+ convention: routes to `blocked_node` if `denial_key` is set in state
75
+ (truthy), otherwise to `allowed_node`.
76
+ """
77
+
78
+ def router(state: Mapping[str, Any]) -> str:
79
+ return blocked_node if state.get(denial_key) is not None else allowed_node
80
+
81
+ return router
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: vsl-langgraph
3
+ Version: 0.1.2
4
+ Summary: LangGraph adapter for VSL (VERBA Specification Language) governance primitives -- implements vsl-core's VSLAdapter conformance contract.
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/supersemantics/VSL-Langgraph
7
+ Project-URL: Repository, https://github.com/supersemantics/VSL-Langgraph
8
+ Project-URL: Changelog, https://github.com/supersemantics/VSL-Langgraph/blob/HEAD/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/supersemantics/VSL-Langgraph/issues
10
+ Keywords: governance,ai-safety,guardrails,langgraph,vsl,verba
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: super-semantics-vsl<0.4,>=0.3
19
+ Requires-Dist: langgraph>=0.2
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7; extra == "dev"
22
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
23
+ Provides-Extra: examples
24
+ Requires-Dist: openai>=1.0; extra == "examples"
25
+ Requires-Dist: anthropic>=0.40; extra == "examples"
26
+ Requires-Dist: google-genai>=0.5; extra == "examples"
27
+ Dynamic: license-file
28
+
29
+ # vsl-langgraph
30
+
31
+ A [LangGraph](https://github.com/langchain-ai/langgraph) adapter for
32
+ [`vsl-core`](https://github.com/supersemantics/VSL-Core): implements `vsl_core.conformance.protocol.VSLAdapter`
33
+ and provides the LangGraph-specific wiring to call a compiled gate from a
34
+ `StateGraph` node and route around a denial.
35
+
36
+ **Is:**
37
+ - A `VSLAdapter` implementation (`LangGraphAdapter`) that passes `vsl_core.conformance.suite.run_conformance_suite` with an empty result.
38
+ - A small set of wiring helpers (`gated_node`, `route_on_denial`) for calling a compiled gate immediately before a node's side effect and routing to a blocked/terminal node via `add_conditional_edges` on denial.
39
+
40
+ **Is not:**
41
+ - A replacement for `vsl-core` itself -- the PreNode/Invariant/Fallback/TerminalState constructs, the ledger, and the governance vocabulary all live in `vsl-core`. This package only compiles and wires those constructs into LangGraph.
42
+ - Automatic interception. LangGraph has no dedicated guardrail/hook type -- a node is a plain callable, so the gate is called explicitly inside `gated_node`, the same way `vsl-core`'s own docs describe calling a gate in plain Python.
43
+
44
+ ## Documentation
45
+
46
+ - [docs/concepts.md](https://github.com/supersemantics/VSL-Langgraph/blob/HEAD/docs/concepts.md) -- the vocabulary (`PreNode`, `Invariant`, `GammaEstimate`, `AssuranceBasis`/F1-F2, `CompiledGate`), a diagram of the gate lifecycle, which gate shape fits which kind of LangGraph node, and a complete scope table for every `vsl-core` construct -- not just the two this adapter compiles.
47
+ - [docs/quickstart.md](https://github.com/supersemantics/VSL-Langgraph/blob/HEAD/docs/quickstart.md) -- install through a working chained-gate graph, building up from one gate to real LLM-backed gates, with real diagrams generated from the actual compiled graphs.
48
+ - [docs/api-reference.md](https://github.com/supersemantics/VSL-Langgraph/blob/HEAD/docs/api-reference.md) -- `LangGraphAdapter` / `gated_node` / `route_on_denial`, every parameter, scannable.
49
+ - [docs/beyond-the-gate.md](https://github.com/supersemantics/VSL-Langgraph/blob/HEAD/docs/beyond-the-gate.md) -- everything `vsl-core` defines that this adapter doesn't compile (`TerminalState`, `VerbaLedger`, re-enablement, `Cluster`, `Specification`/`Proofing`/`Binding`) but that still applies inside a LangGraph app, each with real, run worked examples.
50
+ - [docs/dos-and-donts.md](https://github.com/supersemantics/VSL-Langgraph/blob/HEAD/docs/dos-and-donts.md) -- a scannable checklist for writing or reviewing `vsl-langgraph` code, each rule traced back to a real reason.
51
+ - [docs/testing-your-gates.md](https://github.com/supersemantics/VSL-Langgraph/blob/HEAD/docs/testing-your-gates.md) -- how to prove your own `PreNode`/`Invariant` wiring is correct, generalized from this repo's own test suite.
52
+ - [docs/troubleshooting.md](https://github.com/supersemantics/VSL-Langgraph/blob/HEAD/docs/troubleshooting.md) -- real, reproduced failure modes (not guessed) with exact error text and fixes.
53
+
54
+ ## Install
55
+
56
+ ```
57
+ pip install vsl-langgraph
58
+ ```
59
+
60
+ This installs vsl-core (`super-semantics-vsl`, imported as `vsl_core`) and LangGraph with it. From a
61
+ local clone of this repo: `pip install -e .`
62
+
63
+ ## Quick taste
64
+
65
+ ```python
66
+ gate = LangGraphAdapter().compile_pre_node(pre_node) # or .compile_invariant(invariant)
67
+
68
+ graph.add_node("do_the_thing", gated_node(gate, do_the_thing))
69
+ graph.add_conditional_edges("do_the_thing", route_on_denial("blocked", "allowed"))
70
+ ```
71
+
72
+ `gated_node` calls `gate` immediately before `do_the_thing` runs; on denial, `do_the_thing` never runs and `route_on_denial` sends the graph to `"blocked"` instead. Full walkthrough with real output: [docs/quickstart.md](https://github.com/supersemantics/VSL-Langgraph/blob/HEAD/docs/quickstart.md).
73
+
74
+ ## Sample agents
75
+
76
+ `examples/` is organized by domain -- see [examples/README.md](https://github.com/supersemantics/VSL-Langgraph/blob/HEAD/examples/README.md) for the full list and the roadmap for domains not built yet. Four so far:
77
+
78
+ - **`examples/financial-agents/purchase_approval_agent.py`** -- both governance primitives chained in one graph, synthetic and LLM-free. The fastest read of the gate-wiring pattern.
79
+ - **`examples/marketing/research_publish_agent.py`** -- the same pattern behind five real LLM calls across three providers (Gemini, OpenAI, three Claude models), with an automatic offline fallback when no API keys are set.
80
+ - **`examples/customer-support/refund_support_agent.py`** -- the one with a *dynamic* graph topology: an LLM picks which tool to call (`lookup_order` / `issue_refund` / `send_email`) and a plain LangGraph router sends the run to whichever node it chose, before any vsl-core gate is even reached. `issue_refund` is gated by an `Invariant`, `send_email` by a `PreNode`, `lookup_order` is ungated -- straight from `vsl-core`'s own canonical worked example.
81
+ - **`examples/chat-conversation/chat_reply_agent.py`** -- governance on what the agent is about to *say*, not a backend action: an `Invariant` blocking an unverified guarantee, a `PreNode` handing off to a human on low confidence.
82
+
83
+ ```
84
+ python examples/financial-agents/purchase_approval_agent.py
85
+ python examples/marketing/research_publish_agent.py
86
+ python examples/customer-support/refund_support_agent.py
87
+ python examples/chat-conversation/chat_reply_agent.py
88
+ ```
89
+
90
+ All four run with zero setup (deterministic offline fallbacks) or live against real providers with the right API keys set. Diagrammed and walked through step by step in [docs/quickstart.md](https://github.com/supersemantics/VSL-Langgraph/blob/HEAD/docs/quickstart.md).
91
+
92
+ ## Layout
93
+
94
+ ```
95
+ src/vsl_langgraph/
96
+ ├── adapter.py LangGraphAdapter (VSLAdapter conformance contract)
97
+ └── integration.py gated_node, route_on_denial (StateGraph wiring)
98
+ docs/
99
+ ├── concepts.md vocabulary, gate-lifecycle diagram, full construct scope table
100
+ ├── quickstart.md install -> working graph, with real diagrams
101
+ ├── api-reference.md every export, scannable
102
+ ├── beyond-the-gate.md TerminalState/ledger/escalation, Cluster, Specification -- real worked examples
103
+ ├── dos-and-donts.md scannable checklist for writing/reviewing this code
104
+ ├── testing-your-gates.md how to prove your own gate wiring is correct
105
+ └── troubleshooting.md real, reproduced failure modes
106
+ examples/
107
+ ├── README.md domain taxonomy and roadmap
108
+ ├── financial-agents/
109
+ │ └── purchase_approval_agent.py PreNode + Invariant, no LLM calls
110
+ ├── marketing/
111
+ │ ├── research_publish_agent.py same pattern, 5 real LLM calls / 3 providers
112
+ │ └── marketing_providers.py, marketing_fake_providers.py, marketing_model_ids.py
113
+ ├── customer-support/
114
+ │ ├── refund_support_agent.py LLM picks the tool; dynamic routing, not fixed topology
115
+ │ └── support_providers.py, support_fake_providers.py
116
+ └── chat-conversation/
117
+ ├── chat_reply_agent.py governs the agent's own reply, not a backend action
118
+ └── chat_providers.py, chat_fake_providers.py
119
+ tests/
120
+ ├── test_conformance.py run_conformance_suite(LangGraphAdapter()) == []
121
+ ├── test_integration.py exercises a real compiled StateGraph
122
+ ├── test_purchase_approval_agent.py exercises the financial-agents sample's three scenarios
123
+ ├── test_customer_support_agent.py exercises dynamic routing + both gate types
124
+ └── test_chat_reply_agent.py exercises the chat sample's three scenarios
125
+ ```
126
+
127
+ Every example domain's provider modules are named with a domain prefix (`marketing_*`, `support_*`, `chat_*`) rather than a generic `providers.py` -- several examples import a same-named module in the same test run, and Python's module cache is process-global, so generic names collide across examples.
128
+
129
+ ## Status
130
+
131
+ Alpha. Conformance-suite green; integration tested against a real
132
+ `StateGraph` for both the `PreNode` and `Invariant` paths (pass-through and
133
+ denial routing), including chained multi-gate graphs, real LLM-backed
134
+ gates across three providers, and -- as of `customer-support/refund_support_agent.py`
135
+ -- a graph where an LLM's own output decides which node runs next, not
136
+ just a fixed topology with LLM-backed gates. `gated_node` doesn't implement
137
+ `Fallback`'s retry/intervention fields (`delta_factor`, `max_retries`,
138
+ `intervention`) -- those aren't consumed by `vsl-core`'s own
139
+ `PlainPythonReferenceAdapter` either, so this mirrors the reference
140
+ adapter's actual behavior rather than leaving out something `vsl-core`
141
+ itself defines a contract for.
@@ -0,0 +1,8 @@
1
+ vsl_langgraph/__init__.py,sha256=yZ_PZxJSysMUqYbpZLC8cfaHdSYeDDVn9szl_WZMhSg,217
2
+ vsl_langgraph/adapter.py,sha256=gDA7-0-K8-SEd1YobXp9i5vWk5BGl40exgW7nd3aPzA,7693
3
+ vsl_langgraph/integration.py,sha256=WV4q7K_S-drQIUBj0ENRLzZ7aU65eLT1MsqkjxuXc9w,3039
4
+ vsl_langgraph-0.1.2.dist-info/licenses/LICENSE,sha256=k_93isT77tF7gkFv2yNPM8ESgDazhABxVak4K5tmjs8,1072
5
+ vsl_langgraph-0.1.2.dist-info/METADATA,sha256=S26ZECgStbGxsm0hxVGdKfmeQPVGfrCzsaG222e8Nog,9908
6
+ vsl_langgraph-0.1.2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
7
+ vsl_langgraph-0.1.2.dist-info/top_level.txt,sha256=GV5y6GC7WXksM-U9ra8Uxh3DNakGy_O_HFXnGmhlQhs,14
8
+ vsl_langgraph-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Super Semantics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ vsl_langgraph