metergraph 0.2.1__tar.gz → 0.3.2__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.1 → metergraph-0.3.2}/PKG-INFO +63 -14
- metergraph-0.3.2/README.md +127 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/pyproject.toml +2 -2
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph/__init__.py +37 -15
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph/_capture.py +310 -31
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph/_context.py +89 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph/_template.py +18 -2
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph/_version.py +1 -1
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph.egg-info/PKG-INFO +63 -14
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph.egg-info/SOURCES.txt +1 -0
- metergraph-0.3.2/tests/test_edge_cases.py +556 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/tests/test_real_client_integration.py +101 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/tests/test_sdk.py +326 -14
- metergraph-0.2.1/README.md +0 -78
- {metergraph-0.2.1 → metergraph-0.3.2}/setup.cfg +0 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph/_config.py +0 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph/_failure_log.py +0 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph/_track.py +0 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph/_transport.py +0 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph.egg-info/dependency_links.txt +0 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph.egg-info/requires.txt +0 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/src/metergraph.egg-info/top_level.txt +0 -0
- {metergraph-0.2.1 → metergraph-0.3.2}/tests/test_seam_reality.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metergraph
|
|
3
|
-
Version: 0.2
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Fire-and-forget LLM spend capture for Metergraph
|
|
5
5
|
Author: Pioneer Square Labs
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://www.metergraph.dev/
|
|
8
8
|
Project-URL: Repository, https://github.com/PioneerSquareLabs/metergraphsdk
|
|
9
9
|
Project-URL: Issues, https://github.com/PioneerSquareLabs/metergraphsdk/issues
|
|
10
|
-
Keywords: llm,observability,openai,anthropic,gemini,cost-tracking
|
|
10
|
+
Keywords: llm,observability,openai,anthropic,gemini,vercel-ai-gateway,cost-tracking
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
13
|
Classifier: Typing :: Typed
|
|
@@ -21,7 +21,8 @@ Requires-Dist: google-genai>=1; extra == "dev"
|
|
|
21
21
|
|
|
22
22
|
# metergraph (Python)
|
|
23
23
|
|
|
24
|
-
Zero-runtime-dependency capture for OpenAI, Anthropic, and
|
|
24
|
+
Zero-runtime-dependency capture for OpenAI, Anthropic, Gemini, and Python
|
|
25
|
+
Vercel AI Gateway clients.
|
|
25
26
|
`wrap()` initializes capture from the environment, so setup is one line per
|
|
26
27
|
client; call `metergraph.init(...)` before the first `wrap()` only to pass
|
|
27
28
|
options in code.
|
|
@@ -34,9 +35,10 @@ from openai import OpenAI
|
|
|
34
35
|
client = metergraph.wrap(OpenAI())
|
|
35
36
|
metergraph.set_session("ticket-123")
|
|
36
37
|
|
|
37
|
-
with metergraph.
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
with metergraph.trace("ticket-workflow"):
|
|
39
|
+
with metergraph.route("ticket-classifier", unit="answer"):
|
|
40
|
+
model = metergraph.model_for("ticket-classifier", default="gpt-4.1-mini")
|
|
41
|
+
client.chat.completions.create(model=model, messages=[...])
|
|
40
42
|
|
|
41
43
|
# Emit this after the user-visible task resolves. It shares the bounded async
|
|
42
44
|
# transport and contains no prompt or output content.
|
|
@@ -50,19 +52,59 @@ metergraph.record_outcome(
|
|
|
50
52
|
)
|
|
51
53
|
```
|
|
52
54
|
|
|
55
|
+
Vercel's supported Python surface is AI Gateway through the OpenAI or
|
|
56
|
+
Anthropic SDK. Point either client at the public gateway and `wrap()` detects
|
|
57
|
+
it automatically:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import os
|
|
61
|
+
import metergraph
|
|
62
|
+
from openai import OpenAI
|
|
63
|
+
|
|
64
|
+
gateway = metergraph.wrap(OpenAI(
|
|
65
|
+
api_key=os.getenv("AI_GATEWAY_API_KEY") or os.getenv("VERCEL_OIDC_TOKEN"),
|
|
66
|
+
base_url="https://ai-gateway.vercel.sh/v1",
|
|
67
|
+
))
|
|
68
|
+
|
|
69
|
+
gateway.chat.completions.create(
|
|
70
|
+
model="anthropic/claude-sonnet-4.6",
|
|
71
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Creator-qualified model IDs are normalized for gateway catalog pricing. Sync,
|
|
76
|
+
async, streaming, tool calls, and OpenAI Responses API calls are captured. Use
|
|
77
|
+
`metergraph.wrap(client, provider="vercel")` only when a compatible client is
|
|
78
|
+
behind a custom gateway URL that cannot be detected automatically.
|
|
79
|
+
|
|
53
80
|
Configuration:
|
|
54
81
|
|
|
55
82
|
- `METERGRAPH_APP_TOKEN` — required bearer token
|
|
56
83
|
- `METERGRAPH_INGEST_URL` — optional override; defaults to the hosted HTTPS endpoint
|
|
57
|
-
- `METERGRAPH_CAPTURE_TEXT=
|
|
84
|
+
- `METERGRAPH_CAPTURE_TEXT=0` — opt out of content capture globally
|
|
58
85
|
- `METERGRAPH_DISABLED=1` — process kill switch
|
|
59
86
|
- `METERGRAPH_QUEUE_SIZE`, `METERGRAPH_BATCH_SIZE`, `METERGRAPH_FLUSH_SECONDS`
|
|
60
87
|
|
|
61
88
|
Delivery is bounded and off the request path. Queue overflow or a collector
|
|
62
89
|
outage drops capture and increments internal counters; it never changes the
|
|
63
90
|
provider call. Each wire batch is bounded to 512 KiB after optional gzip.
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
SDK 0.3 captures the scrubbed provider request and a normalized response
|
|
92
|
+
envelope, including assistant content and tool calls, by default. Provider
|
|
93
|
+
credentials and transport headers are removed. Request and response are each
|
|
94
|
+
limited to 100 KiB of UTF-8 with an explicit truncation marker.
|
|
95
|
+
`capture_text=False` on `route()` or `trace()` overrides the global content
|
|
96
|
+
policy for a sensitive operation. The equivalent initialization option is
|
|
97
|
+
`metergraph.init(capture_text=False)`. The public open-source server continues
|
|
98
|
+
to discard content even when the SDK sends it; the hosted dashboard retains
|
|
99
|
+
content under the workspace retention period.
|
|
100
|
+
|
|
101
|
+
`metergraph.trace(name, trace_id=..., parent_span_id=...)` is a sync/async
|
|
102
|
+
context manager and decorator. Calls inside one trace share a trace ID and
|
|
103
|
+
receive distinct span IDs. Calls outside a trace become deterministic
|
|
104
|
+
single-span traces after ingestion. Manual IDs can join work across process
|
|
105
|
+
boundaries; automatic W3C HTTP propagation is not included.
|
|
106
|
+
|
|
107
|
+
Config reads are ETag-aware and fail open to the default model.
|
|
66
108
|
`record_outcome` requires a stable session ID and the model actually used so a
|
|
67
109
|
session-sticky canary can compare task completion and optional feedback,
|
|
68
110
|
turn-count, escalation, abandonment, edit-distance, and regeneration signals.
|
|
@@ -85,14 +127,21 @@ Instrument this codebase's LLM API costs with the `metergraph` PyPI package
|
|
|
85
127
|
(https://github.com/PioneerSquareLabs/metergraphsdk): pip install metergraph,
|
|
86
128
|
then wrap every OpenAI()/AsyncOpenAI(), Anthropic()/AsyncAnthropic(), and
|
|
87
129
|
genai.Client() construction in place, e.g. client = metergraph.wrap(OpenAI()).
|
|
130
|
+
OpenAI or Anthropic clients pointed at https://ai-gateway.vercel.sh are Vercel
|
|
131
|
+
AI Gateway clients and are detected automatically; keep their creator/model ID
|
|
132
|
+
and AI_GATEWAY_API_KEY / VERCEL_OIDC_TOKEN configuration unchanged.
|
|
88
133
|
wrap() returns the same client and initializes itself from the environment:
|
|
89
134
|
METERGRAPH_APP_TOKEN is required (capture is silently off without it) and
|
|
90
135
|
METERGRAPH_INGEST_URL is only for self-hosted servers. Add both to
|
|
91
|
-
.env.example, and never commit a real token.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
136
|
+
.env.example, and never commit a real token. SDK 0.3 captures scrubbed provider
|
|
137
|
+
requests and normalized responses by default for the hosted dashboard; use
|
|
138
|
+
METERGRAPH_CAPTURE_TEXT=0 or capture_text=False around sensitive operations.
|
|
139
|
+
Provider credentials and transport headers must never be captured. Capture is
|
|
140
|
+
fail-open, so do not change call sites, arguments, or error handling; sync,
|
|
141
|
+
async, and streaming work unchanged. Use metergraph.trace("stable-name") to
|
|
142
|
+
group multi-call workflows. Attribution to the calling function is automatic;
|
|
143
|
+
optionally pin stable names on key LLM-calling functions with
|
|
144
|
+
@metergraph.track. On
|
|
96
145
|
serverless, call metergraph.flush() before the handler returns. When done,
|
|
97
146
|
list every client you wrapped and flag LLM calls made outside the official
|
|
98
147
|
openai / anthropic / google-genai SDKs, since those are not captured.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# metergraph (Python)
|
|
2
|
+
|
|
3
|
+
Zero-runtime-dependency capture for OpenAI, Anthropic, Gemini, and Python
|
|
4
|
+
Vercel AI Gateway clients.
|
|
5
|
+
`wrap()` initializes capture from the environment, so setup is one line per
|
|
6
|
+
client; call `metergraph.init(...)` before the first `wrap()` only to pass
|
|
7
|
+
options in code.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import metergraph
|
|
11
|
+
from openai import OpenAI
|
|
12
|
+
|
|
13
|
+
# Anthropic() and google-genai's genai.Client() wrap the same way.
|
|
14
|
+
client = metergraph.wrap(OpenAI())
|
|
15
|
+
metergraph.set_session("ticket-123")
|
|
16
|
+
|
|
17
|
+
with metergraph.trace("ticket-workflow"):
|
|
18
|
+
with metergraph.route("ticket-classifier", unit="answer"):
|
|
19
|
+
model = metergraph.model_for("ticket-classifier", default="gpt-4.1-mini")
|
|
20
|
+
client.chat.completions.create(model=model, messages=[...])
|
|
21
|
+
|
|
22
|
+
# Emit this after the user-visible task resolves. It shares the bounded async
|
|
23
|
+
# transport and contains no prompt or output content.
|
|
24
|
+
metergraph.record_outcome(
|
|
25
|
+
"ticket-classifier",
|
|
26
|
+
model=model,
|
|
27
|
+
task_completed=True,
|
|
28
|
+
feedback_score=1,
|
|
29
|
+
turns_to_resolution=2,
|
|
30
|
+
escalated=False,
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Vercel's supported Python surface is AI Gateway through the OpenAI or
|
|
35
|
+
Anthropic SDK. Point either client at the public gateway and `wrap()` detects
|
|
36
|
+
it automatically:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import os
|
|
40
|
+
import metergraph
|
|
41
|
+
from openai import OpenAI
|
|
42
|
+
|
|
43
|
+
gateway = metergraph.wrap(OpenAI(
|
|
44
|
+
api_key=os.getenv("AI_GATEWAY_API_KEY") or os.getenv("VERCEL_OIDC_TOKEN"),
|
|
45
|
+
base_url="https://ai-gateway.vercel.sh/v1",
|
|
46
|
+
))
|
|
47
|
+
|
|
48
|
+
gateway.chat.completions.create(
|
|
49
|
+
model="anthropic/claude-sonnet-4.6",
|
|
50
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Creator-qualified model IDs are normalized for gateway catalog pricing. Sync,
|
|
55
|
+
async, streaming, tool calls, and OpenAI Responses API calls are captured. Use
|
|
56
|
+
`metergraph.wrap(client, provider="vercel")` only when a compatible client is
|
|
57
|
+
behind a custom gateway URL that cannot be detected automatically.
|
|
58
|
+
|
|
59
|
+
Configuration:
|
|
60
|
+
|
|
61
|
+
- `METERGRAPH_APP_TOKEN` — required bearer token
|
|
62
|
+
- `METERGRAPH_INGEST_URL` — optional override; defaults to the hosted HTTPS endpoint
|
|
63
|
+
- `METERGRAPH_CAPTURE_TEXT=0` — opt out of content capture globally
|
|
64
|
+
- `METERGRAPH_DISABLED=1` — process kill switch
|
|
65
|
+
- `METERGRAPH_QUEUE_SIZE`, `METERGRAPH_BATCH_SIZE`, `METERGRAPH_FLUSH_SECONDS`
|
|
66
|
+
|
|
67
|
+
Delivery is bounded and off the request path. Queue overflow or a collector
|
|
68
|
+
outage drops capture and increments internal counters; it never changes the
|
|
69
|
+
provider call. Each wire batch is bounded to 512 KiB after optional gzip.
|
|
70
|
+
SDK 0.3 captures the scrubbed provider request and a normalized response
|
|
71
|
+
envelope, including assistant content and tool calls, by default. Provider
|
|
72
|
+
credentials and transport headers are removed. Request and response are each
|
|
73
|
+
limited to 100 KiB of UTF-8 with an explicit truncation marker.
|
|
74
|
+
`capture_text=False` on `route()` or `trace()` overrides the global content
|
|
75
|
+
policy for a sensitive operation. The equivalent initialization option is
|
|
76
|
+
`metergraph.init(capture_text=False)`. The public open-source server continues
|
|
77
|
+
to discard content even when the SDK sends it; the hosted dashboard retains
|
|
78
|
+
content under the workspace retention period.
|
|
79
|
+
|
|
80
|
+
`metergraph.trace(name, trace_id=..., parent_span_id=...)` is a sync/async
|
|
81
|
+
context manager and decorator. Calls inside one trace share a trace ID and
|
|
82
|
+
receive distinct span IDs. Calls outside a trace become deterministic
|
|
83
|
+
single-span traces after ingestion. Manual IDs can join work across process
|
|
84
|
+
boundaries; automatic W3C HTTP propagation is not included.
|
|
85
|
+
|
|
86
|
+
Config reads are ETag-aware and fail open to the default model.
|
|
87
|
+
`record_outcome` requires a stable session ID and the model actually used so a
|
|
88
|
+
session-sticky canary can compare task completion and optional feedback,
|
|
89
|
+
turn-count, escalation, abandonment, edit-distance, and regeneration signals.
|
|
90
|
+
|
|
91
|
+
OpenAI Batch API output JSONL is captured per inference when a wrapped
|
|
92
|
+
`client.files.content()` / `retrieve_content()` result is read. Anthropic
|
|
93
|
+
message batches are captured per inference while iterating a wrapped
|
|
94
|
+
`client.messages.batches.results()` result. Run result consumption inside a
|
|
95
|
+
`route()` context so the asynchronous batch retains its product route. Batch
|
|
96
|
+
rows carry real per-result usage and the batch pricing flag; job-management
|
|
97
|
+
polls themselves are not miscounted as model calls.
|
|
98
|
+
|
|
99
|
+
## Set up with an AI coding agent
|
|
100
|
+
|
|
101
|
+
Paste this into Claude Code, Codex, Cursor, or any coding agent inside the
|
|
102
|
+
codebase you want instrumented:
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
Instrument this codebase's LLM API costs with the `metergraph` PyPI package
|
|
106
|
+
(https://github.com/PioneerSquareLabs/metergraphsdk): pip install metergraph,
|
|
107
|
+
then wrap every OpenAI()/AsyncOpenAI(), Anthropic()/AsyncAnthropic(), and
|
|
108
|
+
genai.Client() construction in place, e.g. client = metergraph.wrap(OpenAI()).
|
|
109
|
+
OpenAI or Anthropic clients pointed at https://ai-gateway.vercel.sh are Vercel
|
|
110
|
+
AI Gateway clients and are detected automatically; keep their creator/model ID
|
|
111
|
+
and AI_GATEWAY_API_KEY / VERCEL_OIDC_TOKEN configuration unchanged.
|
|
112
|
+
wrap() returns the same client and initializes itself from the environment:
|
|
113
|
+
METERGRAPH_APP_TOKEN is required (capture is silently off without it) and
|
|
114
|
+
METERGRAPH_INGEST_URL is only for self-hosted servers. Add both to
|
|
115
|
+
.env.example, and never commit a real token. SDK 0.3 captures scrubbed provider
|
|
116
|
+
requests and normalized responses by default for the hosted dashboard; use
|
|
117
|
+
METERGRAPH_CAPTURE_TEXT=0 or capture_text=False around sensitive operations.
|
|
118
|
+
Provider credentials and transport headers must never be captured. Capture is
|
|
119
|
+
fail-open, so do not change call sites, arguments, or error handling; sync,
|
|
120
|
+
async, and streaming work unchanged. Use metergraph.trace("stable-name") to
|
|
121
|
+
group multi-call workflows. Attribution to the calling function is automatic;
|
|
122
|
+
optionally pin stable names on key LLM-calling functions with
|
|
123
|
+
@metergraph.track. On
|
|
124
|
+
serverless, call metergraph.flush() before the handler returns. When done,
|
|
125
|
+
list every client you wrapped and flag LLM calls made outside the official
|
|
126
|
+
openai / anthropic / google-genai SDKs, since those are not captured.
|
|
127
|
+
```
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "metergraph"
|
|
3
|
-
version = "0.2
|
|
3
|
+
version = "0.3.2"
|
|
4
4
|
description = "Fire-and-forget LLM spend capture for Metergraph"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
license = "Apache-2.0"
|
|
8
8
|
authors = [{ name = "Pioneer Square Labs" }]
|
|
9
|
-
keywords = ["llm", "observability", "openai", "anthropic", "gemini", "cost-tracking"]
|
|
9
|
+
keywords = ["llm", "observability", "openai", "anthropic", "gemini", "vercel-ai-gateway", "cost-tracking"]
|
|
10
10
|
classifiers = [
|
|
11
11
|
"Intended Audience :: Developers",
|
|
12
12
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import atexit
|
|
6
6
|
import logging
|
|
7
|
+
import math
|
|
7
8
|
import os
|
|
8
9
|
import uuid
|
|
9
10
|
from datetime import datetime, timezone
|
|
@@ -12,7 +13,7 @@ from typing import Any, Callable
|
|
|
12
13
|
from ._capture import Options, Runtime, set_runtime
|
|
13
14
|
from ._capture import wrap as _wrap
|
|
14
15
|
from ._config import ConfigPoller
|
|
15
|
-
from ._context import route, set_session, set_tags, snapshot, wrap_executor
|
|
16
|
+
from ._context import route, set_session, set_tags, snapshot, trace, wrap_executor
|
|
16
17
|
from ._track import track
|
|
17
18
|
from ._transport import Writer
|
|
18
19
|
from ._version import SDK_VERSION
|
|
@@ -73,7 +74,7 @@ def init(
|
|
|
73
74
|
)
|
|
74
75
|
options = Options(
|
|
75
76
|
capture_text=(
|
|
76
|
-
_env_bool("METERGRAPH_CAPTURE_TEXT",
|
|
77
|
+
_env_bool("METERGRAPH_CAPTURE_TEXT", True)
|
|
77
78
|
if capture_text is None
|
|
78
79
|
else capture_text
|
|
79
80
|
),
|
|
@@ -81,7 +82,17 @@ def init(
|
|
|
81
82
|
app_root=os.path.realpath(app_root or os.getcwd()),
|
|
82
83
|
skip_frames=tuple(skip_frames or ()),
|
|
83
84
|
environment=environment or os.getenv("METERGRAPH_ENV"),
|
|
84
|
-
text_max_bytes=
|
|
85
|
+
text_max_bytes=min(
|
|
86
|
+
100 * 1024,
|
|
87
|
+
max(
|
|
88
|
+
1,
|
|
89
|
+
int(
|
|
90
|
+
os.getenv(
|
|
91
|
+
"METERGRAPH_TEXT_MAX_BYTES", str(100 * 1024)
|
|
92
|
+
)
|
|
93
|
+
),
|
|
94
|
+
),
|
|
95
|
+
),
|
|
85
96
|
)
|
|
86
97
|
set_runtime(Runtime(_writer, options))
|
|
87
98
|
_config = ConfigPoller(
|
|
@@ -105,10 +116,13 @@ def init(
|
|
|
105
116
|
|
|
106
117
|
|
|
107
118
|
def wrap(client: Any, *, provider: str | None = None) -> Any:
|
|
108
|
-
"""Wrap an OpenAI, Anthropic, or
|
|
119
|
+
"""Wrap an OpenAI, Anthropic, Google, or Vercel AI Gateway client.
|
|
109
120
|
|
|
110
121
|
Calls init() automatically, so with env-var configuration this is the
|
|
111
|
-
only setup line needed.
|
|
122
|
+
only setup line needed. OpenAI and Anthropic clients using Vercel's public
|
|
123
|
+
AI Gateway URL are detected automatically; pass ``provider="vercel"`` to
|
|
124
|
+
force gateway handling for a compatible client with a custom URL. Call
|
|
125
|
+
init(...) first to pass Metergraph options in code.
|
|
112
126
|
"""
|
|
113
127
|
init()
|
|
114
128
|
return _wrap(client, provider=provider)
|
|
@@ -144,26 +158,33 @@ def record_outcome(
|
|
|
144
158
|
event_id = str(event_id or uuid.uuid4()).strip()[:128]
|
|
145
159
|
try:
|
|
146
160
|
feedback_score = float(feedback_score) if feedback_score is not None else None
|
|
147
|
-
turns_to_resolution = (
|
|
148
|
-
int(turns_to_resolution) if turns_to_resolution is not None else None
|
|
149
|
-
)
|
|
150
161
|
edit_distance_ratio = (
|
|
151
162
|
float(edit_distance_ratio) if edit_distance_ratio is not None else None
|
|
152
163
|
)
|
|
153
|
-
regeneration_count = (
|
|
154
|
-
int(regeneration_count) if regeneration_count is not None else None
|
|
155
|
-
)
|
|
156
164
|
except (TypeError, ValueError, OverflowError):
|
|
157
165
|
return False
|
|
158
166
|
if not route_name or not model or not session_key or not event_id:
|
|
159
167
|
return False
|
|
160
|
-
if feedback_score is not None and
|
|
168
|
+
if feedback_score is not None and (
|
|
169
|
+
not math.isfinite(feedback_score) or not -1 <= feedback_score <= 1
|
|
170
|
+
):
|
|
161
171
|
return False
|
|
162
|
-
if turns_to_resolution is not None and
|
|
172
|
+
if turns_to_resolution is not None and (
|
|
173
|
+
isinstance(turns_to_resolution, bool)
|
|
174
|
+
or not isinstance(turns_to_resolution, int)
|
|
175
|
+
or not 1 <= turns_to_resolution <= 1_000_000
|
|
176
|
+
):
|
|
163
177
|
return False
|
|
164
|
-
if edit_distance_ratio is not None and
|
|
178
|
+
if edit_distance_ratio is not None and (
|
|
179
|
+
not math.isfinite(edit_distance_ratio)
|
|
180
|
+
or not 0 <= edit_distance_ratio <= 1
|
|
181
|
+
):
|
|
165
182
|
return False
|
|
166
|
-
if regeneration_count is not None and
|
|
183
|
+
if regeneration_count is not None and (
|
|
184
|
+
isinstance(regeneration_count, bool)
|
|
185
|
+
or not isinstance(regeneration_count, int)
|
|
186
|
+
or not 0 <= regeneration_count <= 1_000_000
|
|
187
|
+
):
|
|
167
188
|
return False
|
|
168
189
|
if escalated is not None and not isinstance(escalated, bool):
|
|
169
190
|
return False
|
|
@@ -214,6 +235,7 @@ __all__ = [
|
|
|
214
235
|
"set_tags",
|
|
215
236
|
"shutdown",
|
|
216
237
|
"track",
|
|
238
|
+
"trace",
|
|
217
239
|
"wrap",
|
|
218
240
|
"wrap_executor",
|
|
219
241
|
]
|