metergraph 0.2.0__tar.gz → 0.3.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.
- {metergraph-0.2.0 → metergraph-0.3.0}/PKG-INFO +35 -12
- {metergraph-0.2.0 → metergraph-0.3.0}/README.md +31 -11
- {metergraph-0.2.0 → metergraph-0.3.0}/pyproject.toml +2 -2
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/__init__.py +14 -3
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_capture.py +297 -65
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_config.py +13 -1
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_context.py +89 -0
- metergraph-0.3.0/src/metergraph/_failure_log.py +44 -0
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_template.py +18 -2
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_transport.py +23 -1
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_version.py +1 -1
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph.egg-info/PKG-INFO +35 -12
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph.egg-info/SOURCES.txt +4 -1
- metergraph-0.3.0/src/metergraph.egg-info/requires.txt +6 -0
- metergraph-0.3.0/tests/test_real_client_integration.py +226 -0
- {metergraph-0.2.0 → metergraph-0.3.0}/tests/test_sdk.py +504 -15
- metergraph-0.3.0/tests/test_seam_reality.py +35 -0
- metergraph-0.2.0/src/metergraph.egg-info/requires.txt +0 -3
- {metergraph-0.2.0 → metergraph-0.3.0}/setup.cfg +0 -0
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_track.py +0 -0
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph.egg-info/dependency_links.txt +0 -0
- {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metergraph
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Fire-and-forget LLM spend capture for Metergraph
|
|
5
5
|
Author: Pioneer Square Labs
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -15,6 +15,9 @@ Requires-Python: >=3.10
|
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
18
|
+
Requires-Dist: openai>=2.50.0; extra == "dev"
|
|
19
|
+
Requires-Dist: anthropic>=0.40; extra == "dev"
|
|
20
|
+
Requires-Dist: google-genai>=1; extra == "dev"
|
|
18
21
|
|
|
19
22
|
# metergraph (Python)
|
|
20
23
|
|
|
@@ -31,9 +34,10 @@ from openai import OpenAI
|
|
|
31
34
|
client = metergraph.wrap(OpenAI())
|
|
32
35
|
metergraph.set_session("ticket-123")
|
|
33
36
|
|
|
34
|
-
with metergraph.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
with metergraph.trace("ticket-workflow"):
|
|
38
|
+
with metergraph.route("ticket-classifier", unit="answer"):
|
|
39
|
+
model = metergraph.model_for("ticket-classifier", default="gpt-4.1-mini")
|
|
40
|
+
client.chat.completions.create(model=model, messages=[...])
|
|
37
41
|
|
|
38
42
|
# Emit this after the user-visible task resolves. It shares the bounded async
|
|
39
43
|
# transport and contains no prompt or output content.
|
|
@@ -51,15 +55,30 @@ Configuration:
|
|
|
51
55
|
|
|
52
56
|
- `METERGRAPH_APP_TOKEN` — required bearer token
|
|
53
57
|
- `METERGRAPH_INGEST_URL` — optional override; defaults to the hosted HTTPS endpoint
|
|
54
|
-
- `METERGRAPH_CAPTURE_TEXT=
|
|
58
|
+
- `METERGRAPH_CAPTURE_TEXT=0` — opt out of content capture globally
|
|
55
59
|
- `METERGRAPH_DISABLED=1` — process kill switch
|
|
56
60
|
- `METERGRAPH_QUEUE_SIZE`, `METERGRAPH_BATCH_SIZE`, `METERGRAPH_FLUSH_SECONDS`
|
|
57
61
|
|
|
58
62
|
Delivery is bounded and off the request path. Queue overflow or a collector
|
|
59
63
|
outage drops capture and increments internal counters; it never changes the
|
|
60
64
|
provider call. Each wire batch is bounded to 512 KiB after optional gzip.
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
SDK 0.3 captures the scrubbed provider request and a normalized response
|
|
66
|
+
envelope, including assistant content and tool calls, by default. Provider
|
|
67
|
+
credentials and transport headers are removed. Request and response are each
|
|
68
|
+
limited to 100 KiB of UTF-8 with an explicit truncation marker.
|
|
69
|
+
`capture_text=False` on `route()` or `trace()` overrides the global content
|
|
70
|
+
policy for a sensitive operation. The equivalent initialization option is
|
|
71
|
+
`metergraph.init(capture_text=False)`. The public open-source server continues
|
|
72
|
+
to discard content even when the SDK sends it; the hosted dashboard retains
|
|
73
|
+
content under the workspace retention period.
|
|
74
|
+
|
|
75
|
+
`metergraph.trace(name, trace_id=..., parent_span_id=...)` is a sync/async
|
|
76
|
+
context manager and decorator. Calls inside one trace share a trace ID and
|
|
77
|
+
receive distinct span IDs. Calls outside a trace become deterministic
|
|
78
|
+
single-span traces after ingestion. Manual IDs can join work across process
|
|
79
|
+
boundaries; automatic W3C HTTP propagation is not included.
|
|
80
|
+
|
|
81
|
+
Config reads are ETag-aware and fail open to the default model.
|
|
63
82
|
`record_outcome` requires a stable session ID and the model actually used so a
|
|
64
83
|
session-sticky canary can compare task completion and optional feedback,
|
|
65
84
|
turn-count, escalation, abandonment, edit-distance, and regeneration signals.
|
|
@@ -85,11 +104,15 @@ genai.Client() construction in place, e.g. client = metergraph.wrap(OpenAI()).
|
|
|
85
104
|
wrap() returns the same client and initializes itself from the environment:
|
|
86
105
|
METERGRAPH_APP_TOKEN is required (capture is silently off without it) and
|
|
87
106
|
METERGRAPH_INGEST_URL is only for self-hosted servers. Add both to
|
|
88
|
-
.env.example, and never commit a real token.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
.env.example, and never commit a real token. SDK 0.3 captures scrubbed provider
|
|
108
|
+
requests and normalized responses by default for the hosted dashboard; use
|
|
109
|
+
METERGRAPH_CAPTURE_TEXT=0 or capture_text=False around sensitive operations.
|
|
110
|
+
Provider credentials and transport headers must never be captured. Capture is
|
|
111
|
+
fail-open, so do not change call sites, arguments, or error handling; sync,
|
|
112
|
+
async, and streaming work unchanged. Use metergraph.trace("stable-name") to
|
|
113
|
+
group multi-call workflows. Attribution to the calling function is automatic;
|
|
114
|
+
optionally pin stable names on key LLM-calling functions with
|
|
115
|
+
@metergraph.track. On
|
|
93
116
|
serverless, call metergraph.flush() before the handler returns. When done,
|
|
94
117
|
list every client you wrapped and flag LLM calls made outside the official
|
|
95
118
|
openai / anthropic / google-genai SDKs, since those are not captured.
|
|
@@ -13,9 +13,10 @@ from openai import OpenAI
|
|
|
13
13
|
client = metergraph.wrap(OpenAI())
|
|
14
14
|
metergraph.set_session("ticket-123")
|
|
15
15
|
|
|
16
|
-
with metergraph.
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
with metergraph.trace("ticket-workflow"):
|
|
17
|
+
with metergraph.route("ticket-classifier", unit="answer"):
|
|
18
|
+
model = metergraph.model_for("ticket-classifier", default="gpt-4.1-mini")
|
|
19
|
+
client.chat.completions.create(model=model, messages=[...])
|
|
19
20
|
|
|
20
21
|
# Emit this after the user-visible task resolves. It shares the bounded async
|
|
21
22
|
# transport and contains no prompt or output content.
|
|
@@ -33,15 +34,30 @@ Configuration:
|
|
|
33
34
|
|
|
34
35
|
- `METERGRAPH_APP_TOKEN` — required bearer token
|
|
35
36
|
- `METERGRAPH_INGEST_URL` — optional override; defaults to the hosted HTTPS endpoint
|
|
36
|
-
- `METERGRAPH_CAPTURE_TEXT=
|
|
37
|
+
- `METERGRAPH_CAPTURE_TEXT=0` — opt out of content capture globally
|
|
37
38
|
- `METERGRAPH_DISABLED=1` — process kill switch
|
|
38
39
|
- `METERGRAPH_QUEUE_SIZE`, `METERGRAPH_BATCH_SIZE`, `METERGRAPH_FLUSH_SECONDS`
|
|
39
40
|
|
|
40
41
|
Delivery is bounded and off the request path. Queue overflow or a collector
|
|
41
42
|
outage drops capture and increments internal counters; it never changes the
|
|
42
43
|
provider call. Each wire batch is bounded to 512 KiB after optional gzip.
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
SDK 0.3 captures the scrubbed provider request and a normalized response
|
|
45
|
+
envelope, including assistant content and tool calls, by default. Provider
|
|
46
|
+
credentials and transport headers are removed. Request and response are each
|
|
47
|
+
limited to 100 KiB of UTF-8 with an explicit truncation marker.
|
|
48
|
+
`capture_text=False` on `route()` or `trace()` overrides the global content
|
|
49
|
+
policy for a sensitive operation. The equivalent initialization option is
|
|
50
|
+
`metergraph.init(capture_text=False)`. The public open-source server continues
|
|
51
|
+
to discard content even when the SDK sends it; the hosted dashboard retains
|
|
52
|
+
content under the workspace retention period.
|
|
53
|
+
|
|
54
|
+
`metergraph.trace(name, trace_id=..., parent_span_id=...)` is a sync/async
|
|
55
|
+
context manager and decorator. Calls inside one trace share a trace ID and
|
|
56
|
+
receive distinct span IDs. Calls outside a trace become deterministic
|
|
57
|
+
single-span traces after ingestion. Manual IDs can join work across process
|
|
58
|
+
boundaries; automatic W3C HTTP propagation is not included.
|
|
59
|
+
|
|
60
|
+
Config reads are ETag-aware and fail open to the default model.
|
|
45
61
|
`record_outcome` requires a stable session ID and the model actually used so a
|
|
46
62
|
session-sticky canary can compare task completion and optional feedback,
|
|
47
63
|
turn-count, escalation, abandonment, edit-distance, and regeneration signals.
|
|
@@ -67,11 +83,15 @@ genai.Client() construction in place, e.g. client = metergraph.wrap(OpenAI()).
|
|
|
67
83
|
wrap() returns the same client and initializes itself from the environment:
|
|
68
84
|
METERGRAPH_APP_TOKEN is required (capture is silently off without it) and
|
|
69
85
|
METERGRAPH_INGEST_URL is only for self-hosted servers. Add both to
|
|
70
|
-
.env.example, and never commit a real token.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
.env.example, and never commit a real token. SDK 0.3 captures scrubbed provider
|
|
87
|
+
requests and normalized responses by default for the hosted dashboard; use
|
|
88
|
+
METERGRAPH_CAPTURE_TEXT=0 or capture_text=False around sensitive operations.
|
|
89
|
+
Provider credentials and transport headers must never be captured. Capture is
|
|
90
|
+
fail-open, so do not change call sites, arguments, or error handling; sync,
|
|
91
|
+
async, and streaming work unchanged. Use metergraph.trace("stable-name") to
|
|
92
|
+
group multi-call workflows. Attribution to the calling function is automatic;
|
|
93
|
+
optionally pin stable names on key LLM-calling functions with
|
|
94
|
+
@metergraph.track. On
|
|
75
95
|
serverless, call metergraph.flush() before the handler returns. When done,
|
|
76
96
|
list every client you wrapped and flag LLM calls made outside the official
|
|
77
97
|
openai / anthropic / google-genai SDKs, since those are not captured.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "metergraph"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Fire-and-forget LLM spend capture for Metergraph"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -20,7 +20,7 @@ Repository = "https://github.com/PioneerSquareLabs/metergraphsdk"
|
|
|
20
20
|
Issues = "https://github.com/PioneerSquareLabs/metergraphsdk/issues"
|
|
21
21
|
|
|
22
22
|
[project.optional-dependencies]
|
|
23
|
-
dev = ["pytest>=8"]
|
|
23
|
+
dev = ["pytest>=8", "openai>=2.50.0", "anthropic>=0.40", "google-genai>=1"]
|
|
24
24
|
|
|
25
25
|
[build-system]
|
|
26
26
|
requires = ["setuptools>=68"]
|
|
@@ -12,7 +12,7 @@ from typing import Any, Callable
|
|
|
12
12
|
from ._capture import Options, Runtime, set_runtime
|
|
13
13
|
from ._capture import wrap as _wrap
|
|
14
14
|
from ._config import ConfigPoller
|
|
15
|
-
from ._context import route, set_session, set_tags, snapshot, wrap_executor
|
|
15
|
+
from ._context import route, set_session, set_tags, snapshot, trace, wrap_executor
|
|
16
16
|
from ._track import track
|
|
17
17
|
from ._transport import Writer
|
|
18
18
|
from ._version import SDK_VERSION
|
|
@@ -73,7 +73,7 @@ def init(
|
|
|
73
73
|
)
|
|
74
74
|
options = Options(
|
|
75
75
|
capture_text=(
|
|
76
|
-
_env_bool("METERGRAPH_CAPTURE_TEXT",
|
|
76
|
+
_env_bool("METERGRAPH_CAPTURE_TEXT", True)
|
|
77
77
|
if capture_text is None
|
|
78
78
|
else capture_text
|
|
79
79
|
),
|
|
@@ -81,7 +81,17 @@ def init(
|
|
|
81
81
|
app_root=os.path.realpath(app_root or os.getcwd()),
|
|
82
82
|
skip_frames=tuple(skip_frames or ()),
|
|
83
83
|
environment=environment or os.getenv("METERGRAPH_ENV"),
|
|
84
|
-
text_max_bytes=
|
|
84
|
+
text_max_bytes=min(
|
|
85
|
+
100 * 1024,
|
|
86
|
+
max(
|
|
87
|
+
1,
|
|
88
|
+
int(
|
|
89
|
+
os.getenv(
|
|
90
|
+
"METERGRAPH_TEXT_MAX_BYTES", str(100 * 1024)
|
|
91
|
+
)
|
|
92
|
+
),
|
|
93
|
+
),
|
|
94
|
+
),
|
|
85
95
|
)
|
|
86
96
|
set_runtime(Runtime(_writer, options))
|
|
87
97
|
_config = ConfigPoller(
|
|
@@ -214,6 +224,7 @@ __all__ = [
|
|
|
214
224
|
"set_tags",
|
|
215
225
|
"shutdown",
|
|
216
226
|
"track",
|
|
227
|
+
"trace",
|
|
217
228
|
"wrap",
|
|
218
229
|
"wrap_executor",
|
|
219
230
|
]
|