langchain-ledgerproof 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.
@@ -0,0 +1,9 @@
1
+ ~/.ledgerproof-secrets/
2
+ .env
3
+ *.dmg
4
+
5
+ # TypeScript SDK build artifacts (regenerated by npm run build)
6
+ sdks/*/node_modules/
7
+ sdks/*/dist/
8
+ clients/*/node_modules/
9
+ clients/*/dist/
@@ -0,0 +1,20 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2026 Veronica S. Dawkins / LedgerProof Foundation
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
19
+ For the full text of the Apache License 2.0, see:
20
+ https://www.apache.org/licenses/LICENSE-2.0.txt
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-ledgerproof
3
+ Version: 1.0.0
4
+ Summary: LangChain callback that auto-issues LPR Article 50 receipts for every LLM call.
5
+ Project-URL: Homepage, https://ledgerproofhq.io
6
+ Project-URL: Documentation, https://docs.ledgerproofhq.io/integrations/langchain
7
+ Project-URL: Repository, https://github.com/vsdawkins-creator/ledgerproof-python
8
+ Author-email: "Veronica S. Dawkins" <veronica@ledgerproofhq.io>
9
+ License: Apache License
10
+ Version 2.0, January 2004
11
+ http://www.apache.org/licenses/
12
+
13
+ Copyright 2026 Veronica S. Dawkins / LedgerProof Foundation
14
+
15
+ Licensed under the Apache License, Version 2.0 (the "License");
16
+ you may not use this file except in compliance with the License.
17
+ You may obtain a copy of the License at
18
+
19
+ http://www.apache.org/licenses/LICENSE-2.0
20
+
21
+ Unless required by applicable law or agreed to in writing, software
22
+ distributed under the License is distributed on an "AS IS" BASIS,
23
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24
+ See the License for the specific language governing permissions and
25
+ limitations under the License.
26
+
27
+ For the full text of the Apache License 2.0, see:
28
+ https://www.apache.org/licenses/LICENSE-2.0.txt
29
+ License-File: LICENSE
30
+ Keywords: article-50,compliance,eu-ai-act,langchain,ledgerproof
31
+ Classifier: Development Status :: 5 - Production/Stable
32
+ Classifier: License :: OSI Approved :: Apache Software License
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
35
+ Requires-Python: >=3.9
36
+ Requires-Dist: langchain-core>=0.1
37
+ Requires-Dist: ledgerproof>=1.0
38
+ Description-Content-Type: text/markdown
39
+
40
+ # langchain-ledgerproof
41
+
42
+ LangChain callback that auto-issues EU AI Act Article 50 receipts for every LLM call.
43
+
44
+ ```bash
45
+ pip install langchain-ledgerproof
46
+ ```
47
+
48
+ ```python
49
+ from langchain_anthropic import ChatAnthropic
50
+ from langchain_ledgerproof import LedgerProofCallbackHandler
51
+
52
+ callback = LedgerProofCallbackHandler(
53
+ publisher_id="LEI:5493001KJTIIGC8Y1R12",
54
+ deployer_country="DE",
55
+ deployer_name="Acme Corp",
56
+ )
57
+
58
+ llm = ChatAnthropic(model="claude-sonnet-4-6", callbacks=[callback])
59
+ response = llm.invoke("Write a haiku about Bitcoin.")
60
+ # Receipt issued automatically. Your code is now Article 50 compliant.
61
+ ```
62
+
63
+ Works with any LangChain LLM (Chat or Completion). Each invocation in a chain
64
+ or agent gets its own receipt. Background issuance — your chain isn't blocked.
65
+
66
+ ## Configuration
67
+
68
+ Reads `LEDGERPROOF_API_KEY` from the environment if not passed.
69
+
70
+ See the [main ledgerproof package](../python) for the full Article 50 protocol.
71
+
72
+ ## License
73
+
74
+ Apache-2.0.
@@ -0,0 +1,35 @@
1
+ # langchain-ledgerproof
2
+
3
+ LangChain callback that auto-issues EU AI Act Article 50 receipts for every LLM call.
4
+
5
+ ```bash
6
+ pip install langchain-ledgerproof
7
+ ```
8
+
9
+ ```python
10
+ from langchain_anthropic import ChatAnthropic
11
+ from langchain_ledgerproof import LedgerProofCallbackHandler
12
+
13
+ callback = LedgerProofCallbackHandler(
14
+ publisher_id="LEI:5493001KJTIIGC8Y1R12",
15
+ deployer_country="DE",
16
+ deployer_name="Acme Corp",
17
+ )
18
+
19
+ llm = ChatAnthropic(model="claude-sonnet-4-6", callbacks=[callback])
20
+ response = llm.invoke("Write a haiku about Bitcoin.")
21
+ # Receipt issued automatically. Your code is now Article 50 compliant.
22
+ ```
23
+
24
+ Works with any LangChain LLM (Chat or Completion). Each invocation in a chain
25
+ or agent gets its own receipt. Background issuance — your chain isn't blocked.
26
+
27
+ ## Configuration
28
+
29
+ Reads `LEDGERPROOF_API_KEY` from the environment if not passed.
30
+
31
+ See the [main ledgerproof package](../python) for the full Article 50 protocol.
32
+
33
+ ## License
34
+
35
+ Apache-2.0.
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.21"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "langchain-ledgerproof"
7
+ version = "1.0.0"
8
+ description = "LangChain callback that auto-issues LPR Article 50 receipts for every LLM call."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { file = "LICENSE" }
12
+ authors = [{ name = "Veronica S. Dawkins", email = "veronica@ledgerproofhq.io" }]
13
+ keywords = ["langchain", "ledgerproof", "eu-ai-act", "article-50", "compliance"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "License :: OSI Approved :: Apache Software License",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
19
+ ]
20
+ dependencies = [
21
+ "ledgerproof>=1.0",
22
+ "langchain-core>=0.1",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://ledgerproofhq.io"
27
+ Documentation = "https://docs.ledgerproofhq.io/integrations/langchain"
28
+ Repository = "https://github.com/vsdawkins-creator/ledgerproof-python"
29
+
30
+ [tool.hatch.build.targets.wheel]
31
+ packages = ["src/langchain_ledgerproof"]
@@ -0,0 +1,26 @@
1
+ """LangChain × LedgerProof — auto-issue Article 50 receipts for every LLM call.
2
+
3
+ Usage::
4
+
5
+ from langchain_anthropic import ChatAnthropic
6
+ from langchain_ledgerproof import LedgerProofCallbackHandler
7
+
8
+ callback = LedgerProofCallbackHandler(
9
+ publisher_id="LEI:5493001KJTIIGC8Y1R12",
10
+ deployer_country="DE",
11
+ deployer_name="Acme Corp",
12
+ )
13
+
14
+ llm = ChatAnthropic(model="claude-sonnet-4-6", callbacks=[callback])
15
+ response = llm.invoke("Write a haiku.")
16
+ # The callback issued an LPR receipt for the completion automatically.
17
+
18
+ Works with any LangChain LLM that emits ``on_llm_end`` events.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from ._version import __version__
24
+ from .handler import LedgerProofCallbackHandler
25
+
26
+ __all__ = ["LedgerProofCallbackHandler", "__version__"]
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,173 @@
1
+ """LedgerProofCallbackHandler — a LangChain BaseCallbackHandler.
2
+
3
+ Hooks into the LangChain callbacks system to issue an LPR receipt every
4
+ time an LLM completes. Works for ``ChatModel.invoke``, ``LLM.predict``,
5
+ streaming, and agentic chains (every LLM step gets its own receipt).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from concurrent.futures import ThreadPoolExecutor
12
+ from typing import TYPE_CHECKING, Any
13
+ from uuid import UUID
14
+
15
+ from langchain_core.callbacks.base import BaseCallbackHandler
16
+ from ledgerproof import LedgerProof
17
+ from ledgerproof.errors import LedgerProofError
18
+
19
+ if TYPE_CHECKING:
20
+ from langchain_core.outputs import LLMResult
21
+
22
+ logger = logging.getLogger("langchain_ledgerproof")
23
+
24
+
25
+ class LedgerProofCallbackHandler(BaseCallbackHandler):
26
+ """LangChain callback that issues an LPR Article 50 receipt on every LLM completion.
27
+
28
+ Drop into any LangChain LLM or chain::
29
+
30
+ llm = ChatAnthropic(callbacks=[LedgerProofCallbackHandler(...)])
31
+
32
+ All receipts are issued asynchronously in a background thread. Your chain
33
+ is not blocked. Failures fail open — a warning is logged, your chain
34
+ continues normally.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ *,
40
+ publisher_id: str,
41
+ deployer_country: str,
42
+ deployer_name: str,
43
+ ai_system_id: str | None = None,
44
+ api_key: str | None = None,
45
+ api_base: str | None = None,
46
+ is_public_interest: bool | None = None,
47
+ ) -> None:
48
+ """Initialize the callback.
49
+
50
+ :param publisher_id: Your legal-entity identifier (LEI/EUID/VAT/DID).
51
+ :param deployer_country: ISO 3166-1 alpha-2 country code.
52
+ :param deployer_name: Human-readable organization name.
53
+ :param ai_system_id: Override the AI system identifier in the receipt.
54
+ Default derived from the LangChain LLM metadata.
55
+ :param api_key: LedgerProof API key. Falls back to ``LEDGERPROOF_API_KEY``.
56
+ :param api_base: LedgerProof endpoint. Defaults to api-eu.ledgerproofhq.io.
57
+ :param is_public_interest: Tag every receipt with this assertion.
58
+ """
59
+ self._lp = LedgerProof(
60
+ publisher_id=publisher_id,
61
+ deployer_country=deployer_country,
62
+ api_key=api_key,
63
+ api_base=api_base,
64
+ )
65
+ self._deployer_name = deployer_name
66
+ self._ai_system_id_override = ai_system_id
67
+ self._is_public_interest = is_public_interest
68
+ self._executor = ThreadPoolExecutor(
69
+ max_workers=4, thread_name_prefix="ledgerproof-langchain"
70
+ )
71
+ # Map run_id → invocation context for picking up the right metadata at end.
72
+ self._runs: dict[UUID, dict[str, Any]] = {}
73
+
74
+ # ── LangChain callback hooks ────────────────────────────────────────
75
+
76
+ def on_llm_start(
77
+ self,
78
+ serialized: dict[str, Any],
79
+ prompts: list[str],
80
+ *,
81
+ run_id: UUID,
82
+ **kwargs: Any,
83
+ ) -> None:
84
+ """Stash the LLM identity so we can use it at end-time."""
85
+ model_name = (
86
+ serialized.get("kwargs", {}).get("model")
87
+ or serialized.get("kwargs", {}).get("model_name")
88
+ or serialized.get("name")
89
+ or "langchain/unknown"
90
+ )
91
+ # Infer provider from the serialized.id chain (e.g., ["langchain", "chat_models", "anthropic", "ChatAnthropic"]).
92
+ ids = serialized.get("id", [])
93
+ if isinstance(ids, list) and len(ids) >= 3:
94
+ provider = ids[2]
95
+ else:
96
+ provider = "langchain"
97
+ self._runs[run_id] = {
98
+ "ai_system_id": self._ai_system_id_override or f"{provider}/{model_name}",
99
+ }
100
+
101
+ def on_chat_model_start(
102
+ self,
103
+ serialized: dict[str, Any],
104
+ messages: list[list[Any]],
105
+ *,
106
+ run_id: UUID,
107
+ **kwargs: Any,
108
+ ) -> None:
109
+ """Chat models use this instead of on_llm_start."""
110
+ self.on_llm_start(serialized, [], run_id=run_id, **kwargs)
111
+
112
+ def on_llm_end(self, response: LLMResult, *, run_id: UUID, **kwargs: Any) -> None:
113
+ """Extract the generation text and issue a receipt."""
114
+ context = self._runs.pop(run_id, {})
115
+ ai_system_id = context.get("ai_system_id", "langchain/unknown")
116
+
117
+ # Concatenate all generations' text. For chat models the text is in
118
+ # ``generation.message.content``; for completion LLMs it's in
119
+ # ``generation.text``.
120
+ text = self._extract_text(response)
121
+ if not text:
122
+ return
123
+ self._executor.submit(self._issue_safe, text, ai_system_id)
124
+
125
+ def on_llm_error(self, error: BaseException, *, run_id: UUID, **kwargs: Any) -> None:
126
+ """LLM failed — drop the context, no receipt to issue."""
127
+ self._runs.pop(run_id, None)
128
+
129
+ # ── Internals ───────────────────────────────────────────────────────
130
+
131
+ @staticmethod
132
+ def _extract_text(response: Any) -> str:
133
+ """Pull all generated text out of a LangChain LLMResult."""
134
+ try:
135
+ parts: list[str] = []
136
+ for generation_list in response.generations:
137
+ for gen in generation_list:
138
+ text = getattr(gen, "text", "")
139
+ if text:
140
+ parts.append(text)
141
+ else:
142
+ message = getattr(gen, "message", None)
143
+ if message is not None:
144
+ content = getattr(message, "content", "")
145
+ if isinstance(content, str):
146
+ parts.append(content)
147
+ return "\n".join(parts)
148
+ except (AttributeError, TypeError):
149
+ return ""
150
+
151
+ def _issue_safe(self, text: str, ai_system_id: str) -> Any:
152
+ try:
153
+ return self._lp.publish_ai_article_50(
154
+ artifact=text,
155
+ artifact_content_type="text/plain",
156
+ ai_system_id=ai_system_id,
157
+ deployer_name=self._deployer_name,
158
+ content_category="SYNTHETIC_TEXT",
159
+ generation_type="FULLY_GENERATED",
160
+ is_public_interest=self._is_public_interest,
161
+ )
162
+ except LedgerProofError as exc:
163
+ logger.warning("LedgerProof receipt issuance failed (fail-open): %s", exc)
164
+ return None
165
+
166
+ def __del__(self) -> None:
167
+ try:
168
+ self._executor.shutdown(wait=False)
169
+ except Exception:
170
+ pass
171
+
172
+
173
+ __all__ = ["LedgerProofCallbackHandler"]