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.
Files changed (22) hide show
  1. {metergraph-0.2.0 → metergraph-0.3.0}/PKG-INFO +35 -12
  2. {metergraph-0.2.0 → metergraph-0.3.0}/README.md +31 -11
  3. {metergraph-0.2.0 → metergraph-0.3.0}/pyproject.toml +2 -2
  4. {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/__init__.py +14 -3
  5. {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_capture.py +297 -65
  6. {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_config.py +13 -1
  7. {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_context.py +89 -0
  8. metergraph-0.3.0/src/metergraph/_failure_log.py +44 -0
  9. {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_template.py +18 -2
  10. {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_transport.py +23 -1
  11. {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_version.py +1 -1
  12. {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph.egg-info/PKG-INFO +35 -12
  13. {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph.egg-info/SOURCES.txt +4 -1
  14. metergraph-0.3.0/src/metergraph.egg-info/requires.txt +6 -0
  15. metergraph-0.3.0/tests/test_real_client_integration.py +226 -0
  16. {metergraph-0.2.0 → metergraph-0.3.0}/tests/test_sdk.py +504 -15
  17. metergraph-0.3.0/tests/test_seam_reality.py +35 -0
  18. metergraph-0.2.0/src/metergraph.egg-info/requires.txt +0 -3
  19. {metergraph-0.2.0 → metergraph-0.3.0}/setup.cfg +0 -0
  20. {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph/_track.py +0 -0
  21. {metergraph-0.2.0 → metergraph-0.3.0}/src/metergraph.egg-info/dependency_links.txt +0 -0
  22. {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.2.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.route("ticket-classifier", unit="answer", capture_text=True):
35
- model = metergraph.model_for("ticket-classifier", default="gpt-4.1-mini")
36
- client.chat.completions.create(model=model, messages=[...])
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=1` — opt in to content capture globally; default is metadata-only
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
- `capture_text=True` or `False` on `route()` overrides the global content policy
62
- for that route. Config reads are ETag-aware and fail open to the default model.
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. Capture is metadata-only (tokens,
89
- latency, model, no prompt/completion content) and fail-open, so do not change
90
- call sites, arguments, or error handling; sync, async, and streaming work
91
- unchanged. Attribution to the calling function is automatic; optionally pin
92
- stable names on key LLM-calling functions with @metergraph.track. On
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.route("ticket-classifier", unit="answer", capture_text=True):
17
- model = metergraph.model_for("ticket-classifier", default="gpt-4.1-mini")
18
- client.chat.completions.create(model=model, messages=[...])
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=1` — opt in to content capture globally; default is metadata-only
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
- `capture_text=True` or `False` on `route()` overrides the global content policy
44
- for that route. Config reads are ETag-aware and fail open to the default model.
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. Capture is metadata-only (tokens,
71
- latency, model, no prompt/completion content) and fail-open, so do not change
72
- call sites, arguments, or error handling; sync, async, and streaming work
73
- unchanged. Attribution to the calling function is automatic; optionally pin
74
- stable names on key LLM-calling functions with @metergraph.track. On
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.2.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", False)
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=int(os.getenv("METERGRAPH_TEXT_MAX_BYTES", "100000")),
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
  ]