breeth 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,42 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ .Python
8
+ build/
9
+ develop-eggs/
10
+ dist/
11
+ downloads/
12
+ eggs/
13
+ .eggs/
14
+ lib/
15
+ lib64/
16
+ parts/
17
+ sdist/
18
+ var/
19
+ wheels/
20
+ *.egg-info/
21
+ .installed.cfg
22
+ *.egg
23
+ MANIFEST
24
+
25
+ # Environments
26
+ .venv/
27
+ venv/
28
+ env/
29
+ ENV/
30
+
31
+ # Test / coverage
32
+ .pytest_cache/
33
+ .coverage
34
+ htmlcov/
35
+ .tox/
36
+ .cache
37
+
38
+ # Editors
39
+ .vscode/
40
+ .idea/
41
+ *.swp
42
+ .DS_Store
breeth-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Breeth
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.
breeth-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: breeth
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for Breeth, a memory layer for agents.
5
+ Project-URL: Homepage, https://thebreeth.com
6
+ Project-URL: Documentation, https://docs.thebreeth.com
7
+ Project-URL: Source, https://github.com/Gramies/cogram-sdk-python
8
+ Project-URL: Issues, https://github.com/Gramies/cogram-sdk-python/issues
9
+ Author-email: Breeth <team@thebreeth.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,breeth,graph,knowledge,llm,memory
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx>=0.27
25
+ Requires-Dist: pydantic>=2
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
28
+ Requires-Dist: pytest>=8; extra == 'dev'
29
+ Requires-Dist: respx>=0.21; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # breeth
33
+
34
+ Official Python SDK for [Breeth](https://thebreeth.com), the memory layer for agents.
35
+
36
+ `breeth` is a thin, type-safe wrapper around the Breeth REST API. It ships with both a synchronous and an asynchronous client, runs on Python 3.10+, and depends only on `httpx` and `pydantic`.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install breeth
42
+ ```
43
+
44
+ ## Quickstart (sync)
45
+
46
+ ```python
47
+ from breeth import BreethClient
48
+
49
+ with BreethClient(api_key="ck_live_...") as breeth:
50
+ # Write a memory
51
+ write_resp = breeth.write(
52
+ "Candidate Jane Doe has 4 years HR-tech experience at Workday.",
53
+ group_id="recruiting",
54
+ )
55
+ print(write_resp.episode_name, write_resp.extracted.entities)
56
+
57
+ # Retrieve
58
+ results = breeth.retrieve("HR-tech background", group_id="recruiting", limit=5)
59
+ for edge in results.edges:
60
+ print(edge.fact)
61
+
62
+ # Inspect an entity
63
+ entity = breeth.entity("Workday", mode="narrative")
64
+
65
+ # Browse the graph
66
+ nodes = breeth.graph.list_entities(query="Jane", limit=25)
67
+ edges = breeth.graph.list_edges(limit=50)
68
+ eps = breeth.graph.list_episodes()
69
+ details = breeth.graph.node_details(nodes.entities[0].uuid)
70
+
71
+ # List groups visible to the team
72
+ groups = breeth.groups()
73
+ ```
74
+
75
+ ## Quickstart (async)
76
+
77
+ ```python
78
+ import asyncio
79
+ from breeth import AsyncBreethClient
80
+
81
+ async def main():
82
+ async with AsyncBreethClient(api_key="ck_live_...") as breeth:
83
+ await breeth.write("Recruiter prefers iterative sourcing.")
84
+ results = await breeth.retrieve("recruiter preferences")
85
+ print(results.edges)
86
+
87
+ asyncio.run(main())
88
+ ```
89
+
90
+ ## Configuration
91
+
92
+ | Setting | Source |
93
+ | --- | --- |
94
+ | API key | `api_key=` argument, then `BREETH_API_KEY` env var, then `COGRAM_API_KEY` |
95
+ | Base URL | `base_url=` argument, then `COGRAM_API_URL` env var, default `https://api.thebreeth.com` |
96
+ | End user passthrough | `end_user_id=` argument, sent as `X-End-User-Id` |
97
+
98
+ ## Errors
99
+
100
+ Every non-2xx response is raised as a `BreethError`:
101
+
102
+ ```python
103
+ from breeth import BreethClient, BreethError
104
+
105
+ try:
106
+ with BreethClient(api_key="ck_live_...") as breeth:
107
+ breeth.write("...")
108
+ except BreethError as err:
109
+ print(err.status) # 429
110
+ print(err.slug) # "quota_exceeded"
111
+ print(err.message) # "Monthly write quota exceeded."
112
+ print(err.body) # raw JSON payload
113
+ ```
114
+
115
+ The `slug` field is stable across API versions, so it is safe to branch on it.
116
+
117
+ ## Roadmap
118
+
119
+ - v0.1.0: sync + async clients for write, retrieve, entity, graph, groups.
120
+ - v0.2.0: NDJSON streaming endpoints (`/v1/graph/nodes`, `/v1/graph/links`).
121
+
122
+ ## Links
123
+
124
+ - Product: https://thebreeth.com
125
+ - Docs: https://docs.thebreeth.com
126
+ - Issues: https://github.com/Gramies/cogram-sdk-python/issues
127
+
128
+ ## License
129
+
130
+ MIT. See [LICENSE](LICENSE).
breeth-0.1.0/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # breeth
2
+
3
+ Official Python SDK for [Breeth](https://thebreeth.com), the memory layer for agents.
4
+
5
+ `breeth` is a thin, type-safe wrapper around the Breeth REST API. It ships with both a synchronous and an asynchronous client, runs on Python 3.10+, and depends only on `httpx` and `pydantic`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install breeth
11
+ ```
12
+
13
+ ## Quickstart (sync)
14
+
15
+ ```python
16
+ from breeth import BreethClient
17
+
18
+ with BreethClient(api_key="ck_live_...") as breeth:
19
+ # Write a memory
20
+ write_resp = breeth.write(
21
+ "Candidate Jane Doe has 4 years HR-tech experience at Workday.",
22
+ group_id="recruiting",
23
+ )
24
+ print(write_resp.episode_name, write_resp.extracted.entities)
25
+
26
+ # Retrieve
27
+ results = breeth.retrieve("HR-tech background", group_id="recruiting", limit=5)
28
+ for edge in results.edges:
29
+ print(edge.fact)
30
+
31
+ # Inspect an entity
32
+ entity = breeth.entity("Workday", mode="narrative")
33
+
34
+ # Browse the graph
35
+ nodes = breeth.graph.list_entities(query="Jane", limit=25)
36
+ edges = breeth.graph.list_edges(limit=50)
37
+ eps = breeth.graph.list_episodes()
38
+ details = breeth.graph.node_details(nodes.entities[0].uuid)
39
+
40
+ # List groups visible to the team
41
+ groups = breeth.groups()
42
+ ```
43
+
44
+ ## Quickstart (async)
45
+
46
+ ```python
47
+ import asyncio
48
+ from breeth import AsyncBreethClient
49
+
50
+ async def main():
51
+ async with AsyncBreethClient(api_key="ck_live_...") as breeth:
52
+ await breeth.write("Recruiter prefers iterative sourcing.")
53
+ results = await breeth.retrieve("recruiter preferences")
54
+ print(results.edges)
55
+
56
+ asyncio.run(main())
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ | Setting | Source |
62
+ | --- | --- |
63
+ | API key | `api_key=` argument, then `BREETH_API_KEY` env var, then `COGRAM_API_KEY` |
64
+ | Base URL | `base_url=` argument, then `COGRAM_API_URL` env var, default `https://api.thebreeth.com` |
65
+ | End user passthrough | `end_user_id=` argument, sent as `X-End-User-Id` |
66
+
67
+ ## Errors
68
+
69
+ Every non-2xx response is raised as a `BreethError`:
70
+
71
+ ```python
72
+ from breeth import BreethClient, BreethError
73
+
74
+ try:
75
+ with BreethClient(api_key="ck_live_...") as breeth:
76
+ breeth.write("...")
77
+ except BreethError as err:
78
+ print(err.status) # 429
79
+ print(err.slug) # "quota_exceeded"
80
+ print(err.message) # "Monthly write quota exceeded."
81
+ print(err.body) # raw JSON payload
82
+ ```
83
+
84
+ The `slug` field is stable across API versions, so it is safe to branch on it.
85
+
86
+ ## Roadmap
87
+
88
+ - v0.1.0: sync + async clients for write, retrieve, entity, graph, groups.
89
+ - v0.2.0: NDJSON streaming endpoints (`/v1/graph/nodes`, `/v1/graph/links`).
90
+
91
+ ## Links
92
+
93
+ - Product: https://thebreeth.com
94
+ - Docs: https://docs.thebreeth.com
95
+ - Issues: https://github.com/Gramies/cogram-sdk-python/issues
96
+
97
+ ## License
98
+
99
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "breeth"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for Breeth, a memory layer for agents."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Breeth", email = "team@thebreeth.com" }]
13
+ keywords = ["breeth", "memory", "agents", "llm", "graph", "knowledge"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ ]
26
+ dependencies = [
27
+ "httpx>=0.27",
28
+ "pydantic>=2",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "pytest>=8",
34
+ "pytest-asyncio>=0.23",
35
+ "respx>=0.21",
36
+ ]
37
+
38
+ [project.urls]
39
+ Homepage = "https://thebreeth.com"
40
+ Documentation = "https://docs.thebreeth.com"
41
+ Source = "https://github.com/Gramies/cogram-sdk-python"
42
+ Issues = "https://github.com/Gramies/cogram-sdk-python/issues"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/breeth"]
46
+
47
+ [tool.hatch.build.targets.sdist]
48
+ include = [
49
+ "/src",
50
+ "/tests",
51
+ "/README.md",
52
+ "/LICENSE",
53
+ ]
54
+
55
+ [tool.pytest.ini_options]
56
+ asyncio_mode = "auto"
57
+ testpaths = ["tests"]
@@ -0,0 +1,73 @@
1
+ """Breeth: the official Python SDK for the Breeth memory API.
2
+
3
+ from breeth import BreethClient
4
+
5
+ breeth = BreethClient(api_key="ck_live_...")
6
+ breeth.write("Recruiter prefers candidates with prior HR-tech roles.")
7
+ results = breeth.retrieve("HR-tech background")
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from .client import AsyncBreethClient, BreethClient
12
+ from .errors import BreethError
13
+ from .types import (
14
+ CacheStats,
15
+ CogramTaskEnvelope,
16
+ DirectorProfileBlock,
17
+ EdgeHit,
18
+ EntityEdgeRow,
19
+ EntityEpisodeRow,
20
+ EntityMode,
21
+ EntityNarrativeRow,
22
+ EntityResponse,
23
+ EpisodeMentionRow,
24
+ ExtractedCounts,
25
+ GraphEdgeListResponse,
26
+ GraphEdgeRow,
27
+ GraphEntityListResponse,
28
+ GraphEntityRow,
29
+ GraphEpisodeListResponse,
30
+ GraphEpisodeRow,
31
+ GroupRow,
32
+ GroupsListResponse,
33
+ IntentSuggestion,
34
+ NeighborRow,
35
+ NodeDetailsResponse,
36
+ PatternEvidence,
37
+ RetrieveResponse,
38
+ WriteResponse,
39
+ )
40
+
41
+ __version__ = "0.1.0"
42
+
43
+ __all__ = [
44
+ "AsyncBreethClient",
45
+ "BreethClient",
46
+ "BreethError",
47
+ "CacheStats",
48
+ "CogramTaskEnvelope",
49
+ "DirectorProfileBlock",
50
+ "EdgeHit",
51
+ "EntityEdgeRow",
52
+ "EntityEpisodeRow",
53
+ "EntityMode",
54
+ "EntityNarrativeRow",
55
+ "EntityResponse",
56
+ "EpisodeMentionRow",
57
+ "ExtractedCounts",
58
+ "GraphEdgeListResponse",
59
+ "GraphEdgeRow",
60
+ "GraphEntityListResponse",
61
+ "GraphEntityRow",
62
+ "GraphEpisodeListResponse",
63
+ "GraphEpisodeRow",
64
+ "GroupRow",
65
+ "GroupsListResponse",
66
+ "IntentSuggestion",
67
+ "NeighborRow",
68
+ "NodeDetailsResponse",
69
+ "PatternEvidence",
70
+ "RetrieveResponse",
71
+ "WriteResponse",
72
+ "__version__",
73
+ ]
@@ -0,0 +1,103 @@
1
+ """Shared base utilities for the sync and async Breeth clients.
2
+
3
+ Both `BreethClient` and `AsyncBreethClient` are thin wrappers around
4
+ ``httpx.Client`` / ``httpx.AsyncClient``. This module centralises:
5
+
6
+ * Base URL resolution (env var ``COGRAM_API_URL`` overrides the default).
7
+ * Authorization header construction.
8
+ * Optional ``X-End-User-Id`` passthrough for multi-end-user apps.
9
+ * HTTP error to ``BreethError`` mapping.
10
+
11
+ Note: ``X-Cogram-Team-Id`` is intentionally NOT set. The server resolves
12
+ the team from the ``ck_live_`` API key.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ from typing import Any, Mapping, Optional
18
+
19
+ import httpx
20
+
21
+ from .errors import BreethError
22
+
23
+
24
+ DEFAULT_BASE_URL = "https://api.thebreeth.com"
25
+ API_PREFIX = "/v1"
26
+ DEFAULT_TIMEOUT = 30.0
27
+ USER_AGENT = "breeth-python/0.1.0"
28
+
29
+
30
+ def resolve_base_url(explicit: Optional[str]) -> str:
31
+ """Pick the base URL in order: explicit arg, ``COGRAM_API_URL``,
32
+ SDK default. Trailing slashes are stripped so URL joining is
33
+ predictable."""
34
+ raw = explicit or os.environ.get("COGRAM_API_URL") or DEFAULT_BASE_URL
35
+ return raw.rstrip("/")
36
+
37
+
38
+ def resolve_api_key(explicit: Optional[str]) -> str:
39
+ key = explicit or os.environ.get("BREETH_API_KEY") or os.environ.get("COGRAM_API_KEY")
40
+ if not key:
41
+ raise BreethError(
42
+ "Missing API key. Pass api_key=... or set BREETH_API_KEY.",
43
+ status=0,
44
+ slug="missing_api_key",
45
+ )
46
+ return key
47
+
48
+
49
+ def build_headers(api_key: str, end_user_id: Optional[str]) -> dict[str, str]:
50
+ headers = {
51
+ "Authorization": f"Bearer {api_key}",
52
+ "User-Agent": USER_AGENT,
53
+ "Accept": "application/json",
54
+ }
55
+ if end_user_id:
56
+ headers["X-End-User-Id"] = end_user_id
57
+ return headers
58
+
59
+
60
+ def build_url(base_url: str, path: str) -> str:
61
+ """Join the base URL, the /v1 prefix, and a route path. The path
62
+ can be supplied with or without a leading slash."""
63
+ suffix = path if path.startswith("/") else f"/{path}"
64
+ return f"{base_url}{API_PREFIX}{suffix}"
65
+
66
+
67
+ def raise_for_status(response: httpx.Response) -> None:
68
+ """Map any 4xx / 5xx response to a ``BreethError``.
69
+
70
+ The API returns JSON shaped like
71
+ ``{"detail": {"error": "<slug>", "message": "..."}}`` for handled
72
+ errors, and FastAPI's default ``{"detail": "..."}`` for unhandled
73
+ validation issues. We accept both and extract whatever we can.
74
+ """
75
+ if response.is_success:
76
+ return
77
+
78
+ slug: Optional[str] = None
79
+ message: str = response.reason_phrase or "Request failed"
80
+ body: Any = None
81
+ try:
82
+ body = response.json()
83
+ except Exception:
84
+ body = response.text or None
85
+
86
+ if isinstance(body, dict):
87
+ detail = body.get("detail", body)
88
+ if isinstance(detail, dict):
89
+ slug = detail.get("error") or detail.get("slug") or slug
90
+ message = detail.get("message") or detail.get("detail") or message
91
+ elif isinstance(detail, str):
92
+ message = detail
93
+
94
+ raise BreethError(message, status=response.status_code, slug=slug, body=body)
95
+
96
+
97
+ def merge_query(params: Optional[Mapping[str, Any]]) -> Optional[dict[str, Any]]:
98
+ """Drop None-valued query parameters so callers can pass them
99
+ unconditionally without polluting the URL."""
100
+ if not params:
101
+ return None
102
+ out = {k: v for k, v in params.items() if v is not None}
103
+ return out or None