getopentoken 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.
- getopentoken-0.1.0/PKG-INFO +72 -0
- getopentoken-0.1.0/README.md +25 -0
- getopentoken-0.1.0/getopentoken.egg-info/PKG-INFO +72 -0
- getopentoken-0.1.0/getopentoken.egg-info/SOURCES.txt +33 -0
- getopentoken-0.1.0/getopentoken.egg-info/dependency_links.txt +1 -0
- getopentoken-0.1.0/getopentoken.egg-info/entry_points.txt +2 -0
- getopentoken-0.1.0/getopentoken.egg-info/requires.txt +42 -0
- getopentoken-0.1.0/getopentoken.egg-info/top_level.txt +1 -0
- getopentoken-0.1.0/opentoken/__init__.py +204 -0
- getopentoken-0.1.0/opentoken/_auto/sitecustomize.py +37 -0
- getopentoken-0.1.0/opentoken/_bedrock.py +250 -0
- getopentoken-0.1.0/opentoken/_bedrock_wrapper.py +262 -0
- getopentoken-0.1.0/opentoken/_breaker.py +120 -0
- getopentoken-0.1.0/opentoken/_client.py +424 -0
- getopentoken-0.1.0/opentoken/_context.py +50 -0
- getopentoken-0.1.0/opentoken/_dryrun.py +98 -0
- getopentoken-0.1.0/opentoken/_exporter.py +212 -0
- getopentoken-0.1.0/opentoken/_gateway.py +215 -0
- getopentoken-0.1.0/opentoken/_instrumentation.py +405 -0
- getopentoken-0.1.0/opentoken/_routing.py +186 -0
- getopentoken-0.1.0/opentoken/_transport.py +139 -0
- getopentoken-0.1.0/opentoken/_version.py +1 -0
- getopentoken-0.1.0/opentoken/adapters/__init__.py +1 -0
- getopentoken-0.1.0/opentoken/adapters/langchain.py +17 -0
- getopentoken-0.1.0/opentoken/adapters/llamaindex.py +19 -0
- getopentoken-0.1.0/opentoken/adapters/strands.py +38 -0
- getopentoken-0.1.0/opentoken/instrument.py +75 -0
- getopentoken-0.1.0/opentoken/models.json +66 -0
- getopentoken-0.1.0/opentoken/py.typed +0 -0
- getopentoken-0.1.0/pyproject.toml +53 -0
- getopentoken-0.1.0/setup.cfg +4 -0
- getopentoken-0.1.0/tests/test_failopen.py +350 -0
- getopentoken-0.1.0/tests/test_instrumentation.py +462 -0
- getopentoken-0.1.0/tests/test_integrations.py +281 -0
- getopentoken-0.1.0/tests/test_routing_dryrun.py +196 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: getopentoken
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Open Token SDK: fail-open routing to the Open Token gateway with direct Amazon Bedrock fallback, cost spans, shadow dry-runs and one-line OpenTelemetry auto-instrumentation.
|
|
5
|
+
Author-email: Open Token <support@getopentoken.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://getopentoken.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Provides-Extra: otel
|
|
14
|
+
Requires-Dist: opentelemetry-sdk>=1.25; extra == "otel"
|
|
15
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.25; extra == "otel"
|
|
16
|
+
Provides-Extra: instrument
|
|
17
|
+
Requires-Dist: getopentoken[otel]; extra == "instrument"
|
|
18
|
+
Requires-Dist: opentelemetry-instrumentation-openai>=0.40; extra == "instrument"
|
|
19
|
+
Requires-Dist: opentelemetry-instrumentation-langchain>=0.40; extra == "instrument"
|
|
20
|
+
Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.40; extra == "instrument"
|
|
21
|
+
Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40; extra == "instrument"
|
|
22
|
+
Provides-Extra: bedrock
|
|
23
|
+
Requires-Dist: boto3>=1.34; extra == "bedrock"
|
|
24
|
+
Requires-Dist: getopentoken[otel]; extra == "bedrock"
|
|
25
|
+
Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40; extra == "bedrock"
|
|
26
|
+
Provides-Extra: openai
|
|
27
|
+
Requires-Dist: openai>=1.40; extra == "openai"
|
|
28
|
+
Requires-Dist: httpx>=0.27; extra == "openai"
|
|
29
|
+
Requires-Dist: getopentoken[otel]; extra == "openai"
|
|
30
|
+
Requires-Dist: opentelemetry-instrumentation-openai>=0.40; extra == "openai"
|
|
31
|
+
Provides-Extra: langchain
|
|
32
|
+
Requires-Dist: langchain-openai>=0.2; extra == "langchain"
|
|
33
|
+
Requires-Dist: httpx>=0.27; extra == "langchain"
|
|
34
|
+
Requires-Dist: getopentoken[otel]; extra == "langchain"
|
|
35
|
+
Requires-Dist: opentelemetry-instrumentation-langchain>=0.40; extra == "langchain"
|
|
36
|
+
Provides-Extra: llamaindex
|
|
37
|
+
Requires-Dist: llama-index-llms-openai-like>=0.2; extra == "llamaindex"
|
|
38
|
+
Requires-Dist: httpx>=0.27; extra == "llamaindex"
|
|
39
|
+
Requires-Dist: getopentoken[otel]; extra == "llamaindex"
|
|
40
|
+
Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.40; extra == "llamaindex"
|
|
41
|
+
Provides-Extra: strands
|
|
42
|
+
Requires-Dist: strands-agents>=1.0; extra == "strands"
|
|
43
|
+
Requires-Dist: httpx>=0.27; extra == "strands"
|
|
44
|
+
Requires-Dist: getopentoken[otel]; extra == "strands"
|
|
45
|
+
Provides-Extra: all
|
|
46
|
+
Requires-Dist: getopentoken[bedrock,instrument,openai,otel]; extra == "all"
|
|
47
|
+
|
|
48
|
+
# getopentoken
|
|
49
|
+
|
|
50
|
+
Python SDK for [Open Token](https://getopentoken.com): cost, attribution and fail-open governance for LLM calls. Installed as `getopentoken`, imported as `opentoken`.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install "getopentoken[all]"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import opentoken
|
|
58
|
+
|
|
59
|
+
opentoken.init()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or without code changes: `opentoken-instrument python app.py`.
|
|
63
|
+
|
|
64
|
+
Setup guides for each framework are in the Open Token dashboard under **Docs**.
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cd sdk/python
|
|
70
|
+
uv venv && uv pip install -e ".[all]" langgraph langchain-openai strands-agents
|
|
71
|
+
PYTHONPATH=. python -m unittest discover -s tests -t tests
|
|
72
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# getopentoken
|
|
2
|
+
|
|
3
|
+
Python SDK for [Open Token](https://getopentoken.com): cost, attribution and fail-open governance for LLM calls. Installed as `getopentoken`, imported as `opentoken`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install "getopentoken[all]"
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import opentoken
|
|
11
|
+
|
|
12
|
+
opentoken.init()
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or without code changes: `opentoken-instrument python app.py`.
|
|
16
|
+
|
|
17
|
+
Setup guides for each framework are in the Open Token dashboard under **Docs**.
|
|
18
|
+
|
|
19
|
+
## Development
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd sdk/python
|
|
23
|
+
uv venv && uv pip install -e ".[all]" langgraph langchain-openai strands-agents
|
|
24
|
+
PYTHONPATH=. python -m unittest discover -s tests -t tests
|
|
25
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: getopentoken
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Open Token SDK: fail-open routing to the Open Token gateway with direct Amazon Bedrock fallback, cost spans, shadow dry-runs and one-line OpenTelemetry auto-instrumentation.
|
|
5
|
+
Author-email: Open Token <support@getopentoken.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://getopentoken.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Provides-Extra: otel
|
|
14
|
+
Requires-Dist: opentelemetry-sdk>=1.25; extra == "otel"
|
|
15
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.25; extra == "otel"
|
|
16
|
+
Provides-Extra: instrument
|
|
17
|
+
Requires-Dist: getopentoken[otel]; extra == "instrument"
|
|
18
|
+
Requires-Dist: opentelemetry-instrumentation-openai>=0.40; extra == "instrument"
|
|
19
|
+
Requires-Dist: opentelemetry-instrumentation-langchain>=0.40; extra == "instrument"
|
|
20
|
+
Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.40; extra == "instrument"
|
|
21
|
+
Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40; extra == "instrument"
|
|
22
|
+
Provides-Extra: bedrock
|
|
23
|
+
Requires-Dist: boto3>=1.34; extra == "bedrock"
|
|
24
|
+
Requires-Dist: getopentoken[otel]; extra == "bedrock"
|
|
25
|
+
Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40; extra == "bedrock"
|
|
26
|
+
Provides-Extra: openai
|
|
27
|
+
Requires-Dist: openai>=1.40; extra == "openai"
|
|
28
|
+
Requires-Dist: httpx>=0.27; extra == "openai"
|
|
29
|
+
Requires-Dist: getopentoken[otel]; extra == "openai"
|
|
30
|
+
Requires-Dist: opentelemetry-instrumentation-openai>=0.40; extra == "openai"
|
|
31
|
+
Provides-Extra: langchain
|
|
32
|
+
Requires-Dist: langchain-openai>=0.2; extra == "langchain"
|
|
33
|
+
Requires-Dist: httpx>=0.27; extra == "langchain"
|
|
34
|
+
Requires-Dist: getopentoken[otel]; extra == "langchain"
|
|
35
|
+
Requires-Dist: opentelemetry-instrumentation-langchain>=0.40; extra == "langchain"
|
|
36
|
+
Provides-Extra: llamaindex
|
|
37
|
+
Requires-Dist: llama-index-llms-openai-like>=0.2; extra == "llamaindex"
|
|
38
|
+
Requires-Dist: httpx>=0.27; extra == "llamaindex"
|
|
39
|
+
Requires-Dist: getopentoken[otel]; extra == "llamaindex"
|
|
40
|
+
Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.40; extra == "llamaindex"
|
|
41
|
+
Provides-Extra: strands
|
|
42
|
+
Requires-Dist: strands-agents>=1.0; extra == "strands"
|
|
43
|
+
Requires-Dist: httpx>=0.27; extra == "strands"
|
|
44
|
+
Requires-Dist: getopentoken[otel]; extra == "strands"
|
|
45
|
+
Provides-Extra: all
|
|
46
|
+
Requires-Dist: getopentoken[bedrock,instrument,openai,otel]; extra == "all"
|
|
47
|
+
|
|
48
|
+
# getopentoken
|
|
49
|
+
|
|
50
|
+
Python SDK for [Open Token](https://getopentoken.com): cost, attribution and fail-open governance for LLM calls. Installed as `getopentoken`, imported as `opentoken`.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install "getopentoken[all]"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import opentoken
|
|
58
|
+
|
|
59
|
+
opentoken.init()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or without code changes: `opentoken-instrument python app.py`.
|
|
63
|
+
|
|
64
|
+
Setup guides for each framework are in the Open Token dashboard under **Docs**.
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cd sdk/python
|
|
70
|
+
uv venv && uv pip install -e ".[all]" langgraph langchain-openai strands-agents
|
|
71
|
+
PYTHONPATH=. python -m unittest discover -s tests -t tests
|
|
72
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
getopentoken.egg-info/PKG-INFO
|
|
4
|
+
getopentoken.egg-info/SOURCES.txt
|
|
5
|
+
getopentoken.egg-info/dependency_links.txt
|
|
6
|
+
getopentoken.egg-info/entry_points.txt
|
|
7
|
+
getopentoken.egg-info/requires.txt
|
|
8
|
+
getopentoken.egg-info/top_level.txt
|
|
9
|
+
opentoken/__init__.py
|
|
10
|
+
opentoken/_bedrock.py
|
|
11
|
+
opentoken/_bedrock_wrapper.py
|
|
12
|
+
opentoken/_breaker.py
|
|
13
|
+
opentoken/_client.py
|
|
14
|
+
opentoken/_context.py
|
|
15
|
+
opentoken/_dryrun.py
|
|
16
|
+
opentoken/_exporter.py
|
|
17
|
+
opentoken/_gateway.py
|
|
18
|
+
opentoken/_instrumentation.py
|
|
19
|
+
opentoken/_routing.py
|
|
20
|
+
opentoken/_transport.py
|
|
21
|
+
opentoken/_version.py
|
|
22
|
+
opentoken/instrument.py
|
|
23
|
+
opentoken/models.json
|
|
24
|
+
opentoken/py.typed
|
|
25
|
+
opentoken/_auto/sitecustomize.py
|
|
26
|
+
opentoken/adapters/__init__.py
|
|
27
|
+
opentoken/adapters/langchain.py
|
|
28
|
+
opentoken/adapters/llamaindex.py
|
|
29
|
+
opentoken/adapters/strands.py
|
|
30
|
+
tests/test_failopen.py
|
|
31
|
+
tests/test_instrumentation.py
|
|
32
|
+
tests/test_integrations.py
|
|
33
|
+
tests/test_routing_dryrun.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
|
|
2
|
+
[all]
|
|
3
|
+
getopentoken[bedrock,instrument,openai,otel]
|
|
4
|
+
|
|
5
|
+
[bedrock]
|
|
6
|
+
boto3>=1.34
|
|
7
|
+
getopentoken[otel]
|
|
8
|
+
opentelemetry-instrumentation-bedrock>=0.40
|
|
9
|
+
|
|
10
|
+
[instrument]
|
|
11
|
+
getopentoken[otel]
|
|
12
|
+
opentelemetry-instrumentation-openai>=0.40
|
|
13
|
+
opentelemetry-instrumentation-langchain>=0.40
|
|
14
|
+
opentelemetry-instrumentation-llamaindex>=0.40
|
|
15
|
+
opentelemetry-instrumentation-bedrock>=0.40
|
|
16
|
+
|
|
17
|
+
[langchain]
|
|
18
|
+
langchain-openai>=0.2
|
|
19
|
+
httpx>=0.27
|
|
20
|
+
getopentoken[otel]
|
|
21
|
+
opentelemetry-instrumentation-langchain>=0.40
|
|
22
|
+
|
|
23
|
+
[llamaindex]
|
|
24
|
+
llama-index-llms-openai-like>=0.2
|
|
25
|
+
httpx>=0.27
|
|
26
|
+
getopentoken[otel]
|
|
27
|
+
opentelemetry-instrumentation-llamaindex>=0.40
|
|
28
|
+
|
|
29
|
+
[openai]
|
|
30
|
+
openai>=1.40
|
|
31
|
+
httpx>=0.27
|
|
32
|
+
getopentoken[otel]
|
|
33
|
+
opentelemetry-instrumentation-openai>=0.40
|
|
34
|
+
|
|
35
|
+
[otel]
|
|
36
|
+
opentelemetry-sdk>=1.25
|
|
37
|
+
opentelemetry-exporter-otlp-proto-http>=1.25
|
|
38
|
+
|
|
39
|
+
[strands]
|
|
40
|
+
strands-agents>=1.0
|
|
41
|
+
httpx>=0.27
|
|
42
|
+
getopentoken[otel]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
opentoken
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Open Token SDK: cost and governance for LLM calls, without ever standing in the way of your traffic.
|
|
3
|
+
|
|
4
|
+
import opentoken
|
|
5
|
+
opentoken.init() # reads OT_API_KEY / OT_INGEST_KEY
|
|
6
|
+
client = opentoken.openai_client() # OpenAI SDK client with fail-open routing to Bedrock
|
|
7
|
+
|
|
8
|
+
Customer implementation docs live in the Open Token dashboard (Docs).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
import threading
|
|
16
|
+
from typing import Any, Optional, Sequence, Union
|
|
17
|
+
|
|
18
|
+
from ._breaker import CircuitBreaker
|
|
19
|
+
from ._client import OpenToken, aggregate
|
|
20
|
+
from ._context import Attribution, context, current
|
|
21
|
+
from ._exporter import FanoutExporter, SpanExporter, get_exporter, set_exporter
|
|
22
|
+
from ._instrumentation import (
|
|
23
|
+
ExportTarget,
|
|
24
|
+
TargetSpec,
|
|
25
|
+
TracingState,
|
|
26
|
+
force_flush,
|
|
27
|
+
langfuse_target,
|
|
28
|
+
parse_headers,
|
|
29
|
+
resolve_targets,
|
|
30
|
+
selected_frameworks,
|
|
31
|
+
setup_tracing,
|
|
32
|
+
)
|
|
33
|
+
from ._instrumentation import state as tracing_state
|
|
34
|
+
from ._gateway import GatewayRejected, GatewayUnavailable, MidStreamError
|
|
35
|
+
from ._routing import RemoteRouting, RoutingConfig
|
|
36
|
+
from ._version import __version__
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"Attribution",
|
|
40
|
+
"CircuitBreaker",
|
|
41
|
+
"ExportTarget",
|
|
42
|
+
"GatewayRejected",
|
|
43
|
+
"GatewayUnavailable",
|
|
44
|
+
"MidStreamError",
|
|
45
|
+
"OpenToken",
|
|
46
|
+
"RemoteRouting",
|
|
47
|
+
"RoutingConfig",
|
|
48
|
+
"SpanExporter",
|
|
49
|
+
"__version__",
|
|
50
|
+
"aggregate",
|
|
51
|
+
"async_openai_client",
|
|
52
|
+
"context",
|
|
53
|
+
"current",
|
|
54
|
+
"default_client",
|
|
55
|
+
"init",
|
|
56
|
+
"langfuse_target",
|
|
57
|
+
"openai_client",
|
|
58
|
+
"shutdown",
|
|
59
|
+
"tracing",
|
|
60
|
+
"wrap_bedrock",
|
|
61
|
+
"wrap_openai",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
log = logging.getLogger("opentoken")
|
|
65
|
+
|
|
66
|
+
_lock = threading.Lock()
|
|
67
|
+
_client: Optional[OpenToken] = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def default_otlp_endpoint(base_url: str) -> str:
|
|
71
|
+
"""Open Token's OTLP/HTTP traces endpoint on the same host as the API base URL."""
|
|
72
|
+
root = base_url.rstrip("/")
|
|
73
|
+
root = root[: -len("/v1")] if root.endswith("/v1") else root
|
|
74
|
+
return root + "/api/otel/v1/traces"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _otlp_settings(base_url: str, endpoint: Optional[str], ingest_key: Optional[str]) -> tuple:
|
|
78
|
+
endpoint = endpoint or os.environ.get("OT_OTLP_ENDPOINT") or os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT")
|
|
79
|
+
if not endpoint and os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"):
|
|
80
|
+
endpoint = os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"].rstrip("/") + "/v1/traces"
|
|
81
|
+
if not endpoint:
|
|
82
|
+
endpoint = default_otlp_endpoint(base_url)
|
|
83
|
+
headers = parse_headers(os.environ.get("OTEL_EXPORTER_OTLP_HEADERS"))
|
|
84
|
+
key = ingest_key or os.environ.get("OT_INGEST_KEY")
|
|
85
|
+
if key:
|
|
86
|
+
headers["Authorization"] = f"Bearer {key}"
|
|
87
|
+
project = os.environ.get("OT_PROJECT")
|
|
88
|
+
if project and not any(k.lower() == "x-ot-project" for k in headers):
|
|
89
|
+
headers["X-OT-Project"] = project
|
|
90
|
+
return endpoint, headers
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def init(
|
|
94
|
+
api_key: Optional[str] = None,
|
|
95
|
+
*,
|
|
96
|
+
ingest_key: Optional[str] = None,
|
|
97
|
+
base_url: Optional[str] = None,
|
|
98
|
+
otlp_endpoint: Optional[str] = None,
|
|
99
|
+
service_name: Optional[str] = None,
|
|
100
|
+
instrument: Union[bool, str, Sequence[str], None] = None,
|
|
101
|
+
also_export: Union[None, str, Sequence[TargetSpec]] = None,
|
|
102
|
+
**client_options: Any,
|
|
103
|
+
) -> OpenToken:
|
|
104
|
+
"""
|
|
105
|
+
One line to observe and govern LLM calls:
|
|
106
|
+
|
|
107
|
+
- the SDK's span exporter (bounded queue that drops, never blocks) and the default fail-open client;
|
|
108
|
+
- with `getopentoken[otel]` installed, OpenTelemetry tracing to Open Token plus auto-instrumentation of the installed
|
|
109
|
+
frameworks (OpenAI, LangChain/LangGraph, LlamaIndex, boto3 Bedrock; Strands and AgentCore emit natively).
|
|
110
|
+
`instrument`: True/"all" (default, or OT_INSTRUMENT), False/"none", or a list such as ["openai", "langchain"];
|
|
111
|
+
- `also_export` (or OT_ALSO_EXPORT): send the same traces to more OTLP backends, e.g. "langfuse".
|
|
112
|
+
|
|
113
|
+
`client_options` go to `OpenToken(...)` (routing overrides, bedrock_client, region, model_map, ...). Never raises
|
|
114
|
+
because of telemetry: a misconfigured exporter or instrumentor is logged at debug level and skipped.
|
|
115
|
+
"""
|
|
116
|
+
global _client
|
|
117
|
+
ot = OpenToken(api_key, base_url, **client_options)
|
|
118
|
+
endpoint, headers = _otlp_settings(ot.base_url, otlp_endpoint, ingest_key)
|
|
119
|
+
service = service_name or os.environ.get("OT_SERVICE_NAME") or os.environ.get("OTEL_SERVICE_NAME") or "unknown_service"
|
|
120
|
+
targets = []
|
|
121
|
+
try:
|
|
122
|
+
targets = resolve_targets(also_export)
|
|
123
|
+
except Exception as err:
|
|
124
|
+
log.debug("opentoken: extra export targets skipped: %s", err)
|
|
125
|
+
primary = SpanExporter(endpoint, headers, service_name=service)
|
|
126
|
+
extra = [SpanExporter(t.endpoint, t.headers, service_name=service) for t in targets]
|
|
127
|
+
set_exporter(FanoutExporter([primary, *extra]) if extra else primary)
|
|
128
|
+
frameworks = selected_frameworks(instrument)
|
|
129
|
+
try:
|
|
130
|
+
setup_tracing(endpoint, headers, service, targets, frameworks)
|
|
131
|
+
except Exception as err:
|
|
132
|
+
log.debug("opentoken: auto-instrumentation skipped: %s", err)
|
|
133
|
+
with _lock:
|
|
134
|
+
_client = ot
|
|
135
|
+
return ot
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def tracing() -> TracingState:
|
|
139
|
+
"""What init() set up: whether OTel tracing is on, which frameworks are instrumented, and the export targets."""
|
|
140
|
+
return tracing_state()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def default_client() -> OpenToken:
|
|
144
|
+
global _client
|
|
145
|
+
with _lock:
|
|
146
|
+
if _client is None:
|
|
147
|
+
_client = OpenToken()
|
|
148
|
+
return _client
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def shutdown(timeout_s: float = 2.0) -> None:
|
|
152
|
+
"""Flushes pending spans (call before a short-lived process such as a Lambda handler returns)."""
|
|
153
|
+
exporter = get_exporter()
|
|
154
|
+
if exporter is not None:
|
|
155
|
+
exporter.flush(timeout_s)
|
|
156
|
+
force_flush(timeout_s)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _transports(ot: OpenToken):
|
|
160
|
+
import httpx
|
|
161
|
+
|
|
162
|
+
from ._transport import AsyncOpenTokenTransport, OpenTokenTransport
|
|
163
|
+
|
|
164
|
+
return httpx.Client(transport=OpenTokenTransport(ot), timeout=None), httpx.AsyncClient(
|
|
165
|
+
transport=AsyncOpenTokenTransport(ot), timeout=None
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def openai_client(ot: Optional[OpenToken] = None, **kwargs: Any) -> Any:
|
|
170
|
+
"""`openai.OpenAI` whose chat completions use the fail-open router (gateway or Bedrock direct)."""
|
|
171
|
+
import openai # type: ignore
|
|
172
|
+
|
|
173
|
+
ot = ot or default_client()
|
|
174
|
+
sync, _ = _transports(ot)
|
|
175
|
+
kwargs.setdefault("base_url", ot.base_url)
|
|
176
|
+
kwargs.setdefault("api_key", ot.api_key or "ot-unset")
|
|
177
|
+
return openai.OpenAI(http_client=sync, **kwargs)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def async_openai_client(ot: Optional[OpenToken] = None, **kwargs: Any) -> Any:
|
|
181
|
+
import openai # type: ignore
|
|
182
|
+
|
|
183
|
+
ot = ot or default_client()
|
|
184
|
+
_, async_client = _transports(ot)
|
|
185
|
+
kwargs.setdefault("base_url", ot.base_url)
|
|
186
|
+
kwargs.setdefault("api_key", ot.api_key or "ot-unset")
|
|
187
|
+
return openai.AsyncOpenAI(http_client=async_client, **kwargs)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def wrap_openai(client: Any, ot: Optional[OpenToken] = None) -> Any:
|
|
191
|
+
"""Copy of an existing OpenAI / AsyncOpenAI client that routes chat completions through Open Token."""
|
|
192
|
+
import openai # type: ignore
|
|
193
|
+
|
|
194
|
+
ot = ot or default_client()
|
|
195
|
+
sync, async_client = _transports(ot)
|
|
196
|
+
http_client = async_client if isinstance(client, openai.AsyncOpenAI) else sync
|
|
197
|
+
return client.with_options(http_client=http_client, base_url=ot.base_url, api_key=ot.api_key or client.api_key)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def wrap_bedrock(client: Any, ot: Optional[OpenToken] = None, model: Optional[str] = None) -> Any:
|
|
201
|
+
"""boto3 `bedrock-runtime` client whose converse / converse_stream go through Open Token with fail-open."""
|
|
202
|
+
from ._bedrock_wrapper import BedrockClientWrapper
|
|
203
|
+
|
|
204
|
+
return BedrockClientWrapper(client, ot or default_client(), model)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Loaded at interpreter startup by `opentoken-instrument` (this directory is prepended to PYTHONPATH).
|
|
2
|
+
import importlib.util
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _chain_existing_sitecustomize():
|
|
8
|
+
"""Runs the sitecustomize this one shadows (the app's or the virtualenv's), as opentelemetry-instrument does."""
|
|
9
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
10
|
+
for entry in sys.path:
|
|
11
|
+
if not entry or os.path.abspath(entry) == here:
|
|
12
|
+
continue
|
|
13
|
+
candidate = os.path.join(entry, "sitecustomize.py")
|
|
14
|
+
if os.path.isfile(candidate):
|
|
15
|
+
spec = importlib.util.spec_from_file_location("_opentoken_chained_sitecustomize", candidate)
|
|
16
|
+
if spec and spec.loader:
|
|
17
|
+
try:
|
|
18
|
+
spec.loader.exec_module(importlib.util.module_from_spec(spec))
|
|
19
|
+
except Exception:
|
|
20
|
+
pass
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _auto_init():
|
|
25
|
+
if os.environ.get("OPENTOKEN_AUTO_INIT") != "1":
|
|
26
|
+
return
|
|
27
|
+
try:
|
|
28
|
+
import opentoken
|
|
29
|
+
|
|
30
|
+
opentoken.init()
|
|
31
|
+
except Exception as err: # telemetry must never stop the app from starting
|
|
32
|
+
if os.environ.get("OT_DEBUG"):
|
|
33
|
+
print(f"opentoken-instrument: init skipped: {err}", file=sys.stderr)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_chain_existing_sitecustomize()
|
|
37
|
+
_auto_init()
|