langchain-breakwire 0.1.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ethan Hawkes
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,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-breakwire
3
+ Version: 0.1.0
4
+ Summary: LangChain tools and callback handler for Breakwire: look up API/SDK errors and get the source-linked change that explains them, with the fix.
5
+ Author: Breakwire
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://breakwire.org
8
+ Project-URL: Source, https://github.com/breakwire-org/breakwire/tree/main/packages/langchain-breakwire
9
+ Project-URL: Issues, https://github.com/breakwire-org/breakwire/issues
10
+ Keywords: langchain,breakwire,errors,breaking-changes,deprecations,ai-agents
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: breakwire>=0.1.0
15
+ Requires-Dist: langchain-core>=0.3
16
+ Dynamic: license-file
17
+
18
+ # langchain-breakwire
19
+
20
+ LangChain integration for [Breakwire](https://breakwire.org): tools an agent can call to look up an API/SDK error or
21
+ check upcoming deadlines, and a callback handler that looks up errors raised in your chains, LLM calls and tools.
22
+
23
+ ```bash
24
+ pip install langchain-breakwire
25
+ ```
26
+
27
+ ```python
28
+ from langchain_breakwire import BreakwireLookupTool, BreakwireDeadlinesTool, BreakwireCallbackHandler
29
+
30
+ tools = [BreakwireLookupTool(), BreakwireDeadlinesTool()] # give them to your agent
31
+
32
+ handler = BreakwireCallbackHandler(service="openai-api")
33
+ try:
34
+ chain.invoke(inputs, config={"callbacks": [handler]})
35
+ except Exception as e:
36
+ print(e.breakwire.id, e.breakwire.fix) # same exception, explanation attached
37
+ raise
38
+ ```
39
+
40
+ - `BreakwireLookupTool` (`breakwire_lookup_error`): error text (+ optional service, sdk, sdk_version, model) → compact
41
+ matches with id, confidence, fix, source URL. Unmatched errors are not logged unless `log_miss=True`.
42
+ - `BreakwireDeadlinesTool` (`breakwire_deadlines`): upcoming act-by dates for a service.
43
+ - `BreakwireCallbackHandler`: `on_llm_error` / `on_tool_error` / `on_chain_error`; each exception is looked up once even
44
+ though LangChain reports it at every nesting level. Never raises, never changes the error. `on_match(info, error)` hook.
45
+
46
+ Built on the stdlib-only [`breakwire`](https://pypi.org/project/breakwire/) client, which strips secrets before sending.
47
+ Breakwire's community verification has just started; claims link primary sources, prefer those.
48
+
49
+ MIT.
@@ -0,0 +1,32 @@
1
+ # langchain-breakwire
2
+
3
+ LangChain integration for [Breakwire](https://breakwire.org): tools an agent can call to look up an API/SDK error or
4
+ check upcoming deadlines, and a callback handler that looks up errors raised in your chains, LLM calls and tools.
5
+
6
+ ```bash
7
+ pip install langchain-breakwire
8
+ ```
9
+
10
+ ```python
11
+ from langchain_breakwire import BreakwireLookupTool, BreakwireDeadlinesTool, BreakwireCallbackHandler
12
+
13
+ tools = [BreakwireLookupTool(), BreakwireDeadlinesTool()] # give them to your agent
14
+
15
+ handler = BreakwireCallbackHandler(service="openai-api")
16
+ try:
17
+ chain.invoke(inputs, config={"callbacks": [handler]})
18
+ except Exception as e:
19
+ print(e.breakwire.id, e.breakwire.fix) # same exception, explanation attached
20
+ raise
21
+ ```
22
+
23
+ - `BreakwireLookupTool` (`breakwire_lookup_error`): error text (+ optional service, sdk, sdk_version, model) → compact
24
+ matches with id, confidence, fix, source URL. Unmatched errors are not logged unless `log_miss=True`.
25
+ - `BreakwireDeadlinesTool` (`breakwire_deadlines`): upcoming act-by dates for a service.
26
+ - `BreakwireCallbackHandler`: `on_llm_error` / `on_tool_error` / `on_chain_error`; each exception is looked up once even
27
+ though LangChain reports it at every nesting level. Never raises, never changes the error. `on_match(info, error)` hook.
28
+
29
+ Built on the stdlib-only [`breakwire`](https://pypi.org/project/breakwire/) client, which strips secrets before sending.
30
+ Breakwire's community verification has just started; claims link primary sources, prefer those.
31
+
32
+ MIT.
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "langchain-breakwire"
7
+ version = "0.1.0"
8
+ description = "LangChain tools and callback handler for Breakwire: look up API/SDK errors and get the source-linked change that explains them, with the fix."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "Breakwire" }]
14
+ keywords = ["langchain", "breakwire", "errors", "breaking-changes", "deprecations", "ai-agents"]
15
+ dependencies = ["breakwire>=0.1.0", "langchain-core>=0.3"]
16
+
17
+ [project.urls]
18
+ Homepage = "https://breakwire.org"
19
+ Source = "https://github.com/breakwire-org/breakwire/tree/main/packages/langchain-breakwire"
20
+ Issues = "https://github.com/breakwire-org/breakwire/issues"
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,126 @@
1
+ """LangChain integration for Breakwire: tools the agent can call, and a callback handler that looks up
2
+ errors raised inside chains, LLM calls and tools.
3
+
4
+ from langchain_breakwire import BreakwireLookupTool, BreakwireDeadlinesTool, BreakwireCallbackHandler
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, Dict, List, Optional, Type
9
+ from uuid import UUID
10
+
11
+ from breakwire import Breakwire, explain
12
+ from langchain_core.callbacks import BaseCallbackHandler
13
+ from langchain_core.tools import BaseTool
14
+ from pydantic import BaseModel, ConfigDict, Field
15
+
16
+ __version__ = "0.1.0"
17
+ __all__ = ["BreakwireLookupTool", "BreakwireDeadlinesTool", "BreakwireCallbackHandler", "__version__"]
18
+
19
+
20
+ def _compact(r: Dict[str, Any], max_matches: int = 3) -> Dict[str, Any]:
21
+ cov = r.get("coverage") or {}
22
+ return {
23
+ "lookup_id": r.get("lookup_id"),
24
+ "explained": r.get("explained"),
25
+ "coverage_note": cov.get("note") if cov and not cov.get("covered", True) else None,
26
+ "matches": [
27
+ {"id": m.get("cid"), "title": m.get("title"), "confidence": m.get("confidence"), "fix": m.get("fix"),
28
+ "deadline": m.get("deadline"), "url": m.get("url"), "source_url": m.get("source_url")}
29
+ for m in (r.get("matches") or [])[:max_matches]
30
+ ],
31
+ "note": 'Community data: "signature" = vendor-documented error text matched; "related" = a lead only. Prefer source_url.',
32
+ }
33
+
34
+
35
+ class _LookupInput(BaseModel):
36
+ error: str = Field(description="The error message text. Secrets are stripped before sending.")
37
+ service: Optional[str] = Field(default=None, description="Service slug, e.g. anthropic-api, openai-api, stripe, github-api, mcp")
38
+ sdk: Optional[str] = Field(default=None)
39
+ sdk_version: Optional[str] = Field(default=None)
40
+ model: Optional[str] = Field(default=None)
41
+
42
+
43
+ class BreakwireLookupTool(BaseTool):
44
+ """Look up an API/SDK/platform error in Breakwire and get the claim that explains it, with the fix."""
45
+
46
+ model_config = ConfigDict(arbitrary_types_allowed=True)
47
+ name: str = "breakwire_lookup_error"
48
+ description: str = (
49
+ "Look up an API/SDK/platform error message in Breakwire, a source-linked registry of breaking changes, "
50
+ "deprecations and advisories. Returns the matching claim and its fix. Use when a call fails with an error you do not recognise."
51
+ )
52
+ args_schema: Type[BaseModel] = _LookupInput
53
+ client: Breakwire = Field(default_factory=Breakwire)
54
+ log_miss: bool = False
55
+ max_matches: int = 3
56
+
57
+ def _run(self, error: str, service: Optional[str] = None, sdk: Optional[str] = None,
58
+ sdk_version: Optional[str] = None, model: Optional[str] = None, run_manager: Any = None) -> Dict[str, Any]:
59
+ try:
60
+ r = self.client.lookup(error, service=service, sdk=sdk, version=sdk_version, model=model, log_miss=self.log_miss)
61
+ return _compact(r, self.max_matches)
62
+ except Exception as e: # noqa: BLE001
63
+ return {"error": f"Breakwire unavailable: {e}"}
64
+
65
+
66
+ class _DeadlinesInput(BaseModel):
67
+ service: Optional[str] = Field(default=None, description="Service slug; omit for all services")
68
+
69
+
70
+ class BreakwireDeadlinesTool(BaseTool):
71
+ """Upcoming act-by dates (model retirements, API shutdowns, required migrations), soonest first."""
72
+
73
+ model_config = ConfigDict(arbitrary_types_allowed=True)
74
+ name: str = "breakwire_deadlines"
75
+ description: str = "Upcoming act-by dates (model retirements, API shutdowns, required migrations) for a service, soonest first."
76
+ args_schema: Type[BaseModel] = _DeadlinesInput
77
+ client: Breakwire = Field(default_factory=Breakwire)
78
+
79
+ def _run(self, service: Optional[str] = None, run_manager: Any = None) -> Any:
80
+ try:
81
+ return [{"id": c.get("cid"), "title": c.get("title"), "deadline": c.get("deadline"), "fix": c.get("fix"), "url": c.get("url")}
82
+ for c in self.client.deadlines(service, limit=10)]
83
+ except Exception as e: # noqa: BLE001
84
+ return {"error": f"Breakwire unavailable: {e}"}
85
+
86
+
87
+ class BreakwireCallbackHandler(BaseCallbackHandler):
88
+ """Looks up errors raised in LLM calls, tools and chains; sets ``error.breakwire`` and calls ``on_match``.
89
+
90
+ Each exception is looked up once even though LangChain reports it at every level of nesting.
91
+ Never raises (``raise_error`` is False), never changes the error.
92
+ """
93
+
94
+ raise_error: bool = False
95
+
96
+ def __init__(self, service: Optional[str] = None, client: Optional[Breakwire] = None, timeout: float = 2.5,
97
+ log_miss: bool = True, on_match: Optional[Any] = None, **lookup: Any):
98
+ self.client = client or Breakwire(timeout=timeout)
99
+ self.timeout = timeout
100
+ self.lookup = {"service": service, "log_miss": log_miss, **lookup}
101
+ self.on_match = on_match
102
+ self.results: List[Any] = []
103
+
104
+ def _handle(self, error: BaseException) -> None:
105
+ if not isinstance(error, Exception) or getattr(error, "breakwire", None) is not None:
106
+ return
107
+ info = explain(error, client=self.client, timeout=self.timeout, **{k: v for k, v in self.lookup.items() if v is not None})
108
+ try:
109
+ error.breakwire = info # type: ignore[attr-defined]
110
+ except Exception:
111
+ pass
112
+ self.results.append(info)
113
+ if self.on_match:
114
+ try:
115
+ self.on_match(info, error)
116
+ except Exception:
117
+ pass
118
+
119
+ def on_llm_error(self, error: BaseException, *, run_id: UUID, parent_run_id: Optional[UUID] = None, **kwargs: Any) -> None:
120
+ self._handle(error)
121
+
122
+ def on_tool_error(self, error: BaseException, *, run_id: UUID, parent_run_id: Optional[UUID] = None, **kwargs: Any) -> None:
123
+ self._handle(error)
124
+
125
+ def on_chain_error(self, error: BaseException, *, run_id: UUID, parent_run_id: Optional[UUID] = None, **kwargs: Any) -> None:
126
+ self._handle(error)
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-breakwire
3
+ Version: 0.1.0
4
+ Summary: LangChain tools and callback handler for Breakwire: look up API/SDK errors and get the source-linked change that explains them, with the fix.
5
+ Author: Breakwire
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://breakwire.org
8
+ Project-URL: Source, https://github.com/breakwire-org/breakwire/tree/main/packages/langchain-breakwire
9
+ Project-URL: Issues, https://github.com/breakwire-org/breakwire/issues
10
+ Keywords: langchain,breakwire,errors,breaking-changes,deprecations,ai-agents
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: breakwire>=0.1.0
15
+ Requires-Dist: langchain-core>=0.3
16
+ Dynamic: license-file
17
+
18
+ # langchain-breakwire
19
+
20
+ LangChain integration for [Breakwire](https://breakwire.org): tools an agent can call to look up an API/SDK error or
21
+ check upcoming deadlines, and a callback handler that looks up errors raised in your chains, LLM calls and tools.
22
+
23
+ ```bash
24
+ pip install langchain-breakwire
25
+ ```
26
+
27
+ ```python
28
+ from langchain_breakwire import BreakwireLookupTool, BreakwireDeadlinesTool, BreakwireCallbackHandler
29
+
30
+ tools = [BreakwireLookupTool(), BreakwireDeadlinesTool()] # give them to your agent
31
+
32
+ handler = BreakwireCallbackHandler(service="openai-api")
33
+ try:
34
+ chain.invoke(inputs, config={"callbacks": [handler]})
35
+ except Exception as e:
36
+ print(e.breakwire.id, e.breakwire.fix) # same exception, explanation attached
37
+ raise
38
+ ```
39
+
40
+ - `BreakwireLookupTool` (`breakwire_lookup_error`): error text (+ optional service, sdk, sdk_version, model) → compact
41
+ matches with id, confidence, fix, source URL. Unmatched errors are not logged unless `log_miss=True`.
42
+ - `BreakwireDeadlinesTool` (`breakwire_deadlines`): upcoming act-by dates for a service.
43
+ - `BreakwireCallbackHandler`: `on_llm_error` / `on_tool_error` / `on_chain_error`; each exception is looked up once even
44
+ though LangChain reports it at every nesting level. Never raises, never changes the error. `on_match(info, error)` hook.
45
+
46
+ Built on the stdlib-only [`breakwire`](https://pypi.org/project/breakwire/) client, which strips secrets before sending.
47
+ Breakwire's community verification has just started; claims link primary sources, prefer those.
48
+
49
+ MIT.
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/langchain_breakwire/__init__.py
5
+ src/langchain_breakwire.egg-info/PKG-INFO
6
+ src/langchain_breakwire.egg-info/SOURCES.txt
7
+ src/langchain_breakwire.egg-info/dependency_links.txt
8
+ src/langchain_breakwire.egg-info/requires.txt
9
+ src/langchain_breakwire.egg-info/top_level.txt
10
+ tests/test_unit.py
@@ -0,0 +1,2 @@
1
+ breakwire>=0.1.0
2
+ langchain-core>=0.3
@@ -0,0 +1 @@
1
+ langchain_breakwire
@@ -0,0 +1,72 @@
1
+ """Offline tests with a fake Breakwire client. Run: python -m unittest discover -s tests"""
2
+ import pathlib
3
+ import sys
4
+ import unittest
5
+
6
+ ROOT = pathlib.Path(__file__).resolve().parents[1]
7
+ sys.path.insert(0, str(ROOT / "src"))
8
+ sys.path.insert(0, str(ROOT.parent / "python" / "src"))
9
+
10
+ from langchain_core.runnables import RunnableLambda # noqa: E402
11
+ from breakwire import Breakwire # noqa: E402
12
+ from langchain_breakwire import BreakwireCallbackHandler, BreakwireDeadlinesTool, BreakwireLookupTool # noqa: E402
13
+
14
+ LOOKUP = {"lookup_id": "lk_x", "explained": True, "coverage": None,
15
+ "matches": [{"cid": "AN-2026-0025", "title": "T", "confidence": "signature", "fix": "F", "url": "U", "source_url": "S", "deadline": None}]}
16
+
17
+
18
+ class Fake(Breakwire):
19
+ def __init__(self, fail=False):
20
+ super().__init__(base_url="http://fake.invalid")
21
+ self.calls, self.fail = [], fail
22
+
23
+ def lookup(self, error, **kw):
24
+ self.calls.append(("lookup", str(error), kw))
25
+ if self.fail:
26
+ raise RuntimeError("down")
27
+ return LOOKUP
28
+
29
+ def deadlines(self, service=None, **kw):
30
+ self.calls.append(("deadlines", service, kw))
31
+ return [{"cid": "AN-2026-0004", "title": "D", "deadline": "2026-11-30", "fix": "F", "url": "U"}]
32
+
33
+
34
+ class T(unittest.TestCase):
35
+ def test_lookup_tool(self):
36
+ f = Fake()
37
+ t = BreakwireLookupTool(client=f)
38
+ out = t.invoke({"error": "boom", "service": "anthropic-api"})
39
+ self.assertEqual(out["matches"][0]["id"], "AN-2026-0025")
40
+ self.assertEqual(f.calls[0][2]["service"], "anthropic-api")
41
+ self.assertFalse(f.calls[0][2]["log_miss"])
42
+ self.assertIn("error", t.args)
43
+
44
+ def test_lookup_tool_never_raises(self):
45
+ out = BreakwireLookupTool(client=Fake(fail=True)).invoke({"error": "boom"})
46
+ self.assertIn("unavailable", out["error"])
47
+
48
+ def test_deadlines_tool(self):
49
+ out = BreakwireDeadlinesTool(client=Fake()).invoke({"service": "anthropic-api"})
50
+ self.assertEqual(out[0]["deadline"], "2026-11-30")
51
+
52
+ def test_callback_handler_once_per_exception(self):
53
+ f = Fake()
54
+ h = BreakwireCallbackHandler(service="openai-api", client=f)
55
+ inner = RunnableLambda(lambda x: (_ for _ in ()).throw(ValueError("Error code: 429 organization_spend_limit_exceeded")))
56
+ chain = RunnableLambda(lambda x: x) | inner
57
+ with self.assertRaises(ValueError) as cm:
58
+ chain.invoke("hi", config={"callbacks": [h]})
59
+ self.assertEqual(cm.exception.breakwire.id, "AN-2026-0025")
60
+ self.assertEqual(len([c for c in f.calls if c[0] == "lookup"]), 1)
61
+
62
+ def test_callback_handler_never_masks(self):
63
+ h = BreakwireCallbackHandler(client=Fake(fail=True))
64
+ err = KeyError("x")
65
+ with self.assertRaises(KeyError) as cm:
66
+ RunnableLambda(lambda x: (_ for _ in ()).throw(err)).invoke(1, config={"callbacks": [h]})
67
+ self.assertIs(cm.exception, err)
68
+ self.assertIsNone(err.breakwire.id)
69
+
70
+
71
+ if __name__ == "__main__":
72
+ unittest.main()