trodo-python 2.22.0__py3-none-any.whl → 2.23.1__py3-none-any.whl
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.
- trodo/otel/auto_instrument.py +49 -1
- trodo/prompts/compile.py +38 -2
- trodo/prompts/template.py +16 -1
- {trodo_python-2.22.0.dist-info → trodo_python-2.23.1.dist-info}/METADATA +1 -1
- {trodo_python-2.22.0.dist-info → trodo_python-2.23.1.dist-info}/RECORD +7 -7
- {trodo_python-2.22.0.dist-info → trodo_python-2.23.1.dist-info}/WHEEL +0 -0
- {trodo_python-2.22.0.dist-info → trodo_python-2.23.1.dist-info}/top_level.txt +0 -0
trodo/otel/auto_instrument.py
CHANGED
|
@@ -438,7 +438,44 @@ def _instrument(module_name: str, *preferred: str) -> None:
|
|
|
438
438
|
)
|
|
439
439
|
if cls is None:
|
|
440
440
|
raise ImportError(f"no Instrumentor class exported by {module_name}")
|
|
441
|
-
|
|
441
|
+
|
|
442
|
+
instr = cls()
|
|
443
|
+
|
|
444
|
+
# An instrumentor whose target library is the wrong VERSION does not raise:
|
|
445
|
+
# OpenTelemetry logs a DependencyConflict and returns without patching
|
|
446
|
+
# anything. We would then append it to `active` and report auto-capture that
|
|
447
|
+
# is not happening -- the same lie as listing a framework whose
|
|
448
|
+
# instrumentation cannot patch the installed SDK. Ask first, and let the
|
|
449
|
+
# caller report it honestly.
|
|
450
|
+
conflict = _dependency_conflict(instr)
|
|
451
|
+
if conflict is not None:
|
|
452
|
+
raise _IncompatibleInstrumentation(str(conflict))
|
|
453
|
+
|
|
454
|
+
instr.instrument()
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class _IncompatibleInstrumentation(ImportError):
|
|
458
|
+
"""The instrumentor loaded, but its target library is an unsupported version."""
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _dependency_conflict(instr: Any) -> Any:
|
|
462
|
+
"""The version conflict OpenTelemetry would hit, or None.
|
|
463
|
+
|
|
464
|
+
Returns None when we cannot tell -- an older OTel without the helper, or an
|
|
465
|
+
instrumentor that declares nothing. Falsely reporting a working setup as
|
|
466
|
+
broken is worse than the silence this replaces.
|
|
467
|
+
"""
|
|
468
|
+
try:
|
|
469
|
+
from opentelemetry.instrumentation.dependencies import ( # type: ignore
|
|
470
|
+
get_dependency_conflicts,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
deps = instr.instrumentation_dependencies()
|
|
474
|
+
if not deps:
|
|
475
|
+
return None
|
|
476
|
+
return get_dependency_conflicts(deps)
|
|
477
|
+
except Exception: # noqa: BLE001
|
|
478
|
+
return None
|
|
442
479
|
|
|
443
480
|
|
|
444
481
|
_INSTRUMENTORS: List[tuple[str, Callable[[], Any]]] = []
|
|
@@ -556,6 +593,17 @@ def enable_auto_instrument(
|
|
|
556
593
|
try:
|
|
557
594
|
register()
|
|
558
595
|
active.append(name)
|
|
596
|
+
except _IncompatibleInstrumentation as e:
|
|
597
|
+
# Loud, because this is the case where everything LOOKS installed:
|
|
598
|
+
# the package is present, the import worked, and no spans will ever
|
|
599
|
+
# appear. Silence here is what costs people an afternoon.
|
|
600
|
+
_warn_once(
|
|
601
|
+
f"version-{name}",
|
|
602
|
+
f"auto-instrument: {name} will NOT be auto-captured and is not "
|
|
603
|
+
f"reported as active -- {e}. Wrap the call with trodo.llm(...) "
|
|
604
|
+
f"to capture it, or pin the library to a supported version.",
|
|
605
|
+
)
|
|
606
|
+
continue
|
|
559
607
|
except Exception:
|
|
560
608
|
continue
|
|
561
609
|
return active
|
trodo/prompts/compile.py
CHANGED
|
@@ -36,6 +36,36 @@ def _q(value: Any) -> str:
|
|
|
36
36
|
except (TypeError, ValueError):
|
|
37
37
|
return repr(value)
|
|
38
38
|
|
|
39
|
+
def _js_string(value: Any) -> str:
|
|
40
|
+
"""JavaScript ``String()`` semantics, byte-for-byte.
|
|
41
|
+
|
|
42
|
+
A non-string passed for a string-typed variable is a caller type mismatch,
|
|
43
|
+
but all three engines must agree on what it renders as -- the backend
|
|
44
|
+
reference (which powers the playground) and the Node SDK both go through
|
|
45
|
+
JS ``String()``, so ``True`` must render ``true``, ``3.0`` must render
|
|
46
|
+
``3``, a list joins with commas, and a dict renders the infamous
|
|
47
|
+
``[object Object]``. The JSON rendering callers actually want lives on the
|
|
48
|
+
``json`` variable type, where all three engines already emit compact JSON.
|
|
49
|
+
"""
|
|
50
|
+
if value is None:
|
|
51
|
+
return ""
|
|
52
|
+
if isinstance(value, bool):
|
|
53
|
+
return "true" if value else "false"
|
|
54
|
+
if isinstance(value, float) and value.is_integer() and abs(value) < 1e21:
|
|
55
|
+
return str(int(value))
|
|
56
|
+
if isinstance(value, (int, float)):
|
|
57
|
+
return str(value)
|
|
58
|
+
if isinstance(value, str):
|
|
59
|
+
return value
|
|
60
|
+
if isinstance(value, (list, tuple)):
|
|
61
|
+
# Array.prototype.toString: elements joined by ',', null/undefined
|
|
62
|
+
# rendering empty, recursively.
|
|
63
|
+
return ",".join(_js_string(x) for x in value)
|
|
64
|
+
if isinstance(value, dict):
|
|
65
|
+
return "[object Object]"
|
|
66
|
+
return str(value)
|
|
67
|
+
|
|
68
|
+
|
|
39
69
|
def _coerce(value: Any, type_: str, name: str, errors: List[str]) -> Any:
|
|
40
70
|
"""Coerce a caller value to the declared type.
|
|
41
71
|
|
|
@@ -71,7 +101,7 @@ def _coerce(value: Any, type_: str, name: str, errors: List[str]) -> Any:
|
|
|
71
101
|
return value
|
|
72
102
|
|
|
73
103
|
if type_ == "string":
|
|
74
|
-
return value if isinstance(value, str) else
|
|
104
|
+
return value if isinstance(value, str) else _js_string(value)
|
|
75
105
|
|
|
76
106
|
if type_ == "messages":
|
|
77
107
|
if not isinstance(value, list):
|
|
@@ -119,7 +149,13 @@ def build_scope(
|
|
|
119
149
|
# empty. That IS the point of declaring a default -- there is no third
|
|
120
150
|
# case where the caller has to have supplied something, and adding one
|
|
121
151
|
# would turn an empty render into a crash for no gain.
|
|
122
|
-
|
|
152
|
+
#
|
|
153
|
+
# The default applies to an ABSENT key only. An explicit ``None`` is a
|
|
154
|
+
# value -- "render this empty" -- exactly as ``null`` is in the backend
|
|
155
|
+
# reference engine and the Node SDK. This engine used to substitute the
|
|
156
|
+
# default for an explicit ``None``, so the same call rendered different
|
|
157
|
+
# text in Python than everywhere else, including the playground.
|
|
158
|
+
if not has:
|
|
123
159
|
value = default if has_default else ([] if type_ == "messages" else "")
|
|
124
160
|
|
|
125
161
|
scope[name] = _coerce(value, type_, name, errors)
|
trodo/prompts/template.py
CHANGED
|
@@ -159,6 +159,21 @@ def _lookup(path: str, scopes: Sequence[Any]) -> Tuple[bool, Any]:
|
|
|
159
159
|
return False, None
|
|
160
160
|
|
|
161
161
|
|
|
162
|
+
def _jsonable(value):
|
|
163
|
+
"""Normalise for JSON.stringify parity: JS has one number type, so a float
|
|
164
|
+
that is a whole number must serialise as ``1`` and not ``1.0`` -- at any
|
|
165
|
+
depth. Everything else passes through untouched."""
|
|
166
|
+
if isinstance(value, bool):
|
|
167
|
+
return value
|
|
168
|
+
if isinstance(value, float) and value.is_integer() and abs(value) < 1e21:
|
|
169
|
+
return int(value)
|
|
170
|
+
if isinstance(value, dict):
|
|
171
|
+
return {k: _jsonable(v) for k, v in value.items()}
|
|
172
|
+
if isinstance(value, (list, tuple)):
|
|
173
|
+
return [_jsonable(v) for v in value]
|
|
174
|
+
return value
|
|
175
|
+
|
|
176
|
+
|
|
162
177
|
def _stringify(value: Any) -> str:
|
|
163
178
|
if value is None:
|
|
164
179
|
return ""
|
|
@@ -173,7 +188,7 @@ def _stringify(value: Any) -> str:
|
|
|
173
188
|
return str(int(value))
|
|
174
189
|
return str(value)
|
|
175
190
|
try:
|
|
176
|
-
return json.dumps(value, separators=(",", ":"))
|
|
191
|
+
return json.dumps(_jsonable(value), separators=(",", ":"), ensure_ascii=False)
|
|
177
192
|
except (TypeError, ValueError):
|
|
178
193
|
return str(value)
|
|
179
194
|
|
|
@@ -16,7 +16,7 @@ trodo/managers/people_manager.py,sha256=mMVnx40Mlifx6NGgChvohC9ViK6dQu2mkXNHbV8p
|
|
|
16
16
|
trodo/managers/prompt_manager.py,sha256=qFVHgh7PW-SlgHp62ybirdYsJBg-fcQVMTTU3jPqFvo,13435
|
|
17
17
|
trodo/managers/user_manager.py,sha256=faJYX3CHrD7ulYiShV7FhThSMX9aJ3kdOgU4Qtdy5FM,2844
|
|
18
18
|
trodo/otel/__init__.py,sha256=yiRFXWUU45bAM2CV37XeO7zf1hmnmjufdP4XO50yEyE,624
|
|
19
|
-
trodo/otel/auto_instrument.py,sha256=
|
|
19
|
+
trodo/otel/auto_instrument.py,sha256=dA7IZ9cTSNTFK-oVwkYL2OwfTH3wDKM4iKXtPvZoK-Y,23800
|
|
20
20
|
trodo/otel/context.py,sha256=Jd0aTc0Q-1dM5kXXinhZD8YtnpdKgvneNiODyboVGKY,1418
|
|
21
21
|
trodo/otel/helpers.py,sha256=XOMWcgZHaq5SQbkFxDaXPE4CDFjn01xjJmJ1vIxvwpw,20730
|
|
22
22
|
trodo/otel/processor.py,sha256=LKlXxP3BeQ7DP8SzYAgXZZOeJ6-6b7e43i19gCUC_0Y,7939
|
|
@@ -25,8 +25,8 @@ trodo/otel/register.py,sha256=bV_ePTfUvPugig2GZnylhzxi2QfoPWs96A9mGLPMrSQ,9387
|
|
|
25
25
|
trodo/otel/transport.py,sha256=hzZz8gwSMGJ8CxdijmLn1Ljt18owr9XTWy13DLbwYbw,2441
|
|
26
26
|
trodo/otel/wrap_agent.py,sha256=Izty1jxF4ANVqS72L0CR9jmMuW372AApaEO_0LC5Thw,43860
|
|
27
27
|
trodo/prompts/__init__.py,sha256=yunNc8WkTSfEEkRR-NZWXAt_ZB3Ee2INRh-4RE7uHj4,806
|
|
28
|
-
trodo/prompts/compile.py,sha256=
|
|
29
|
-
trodo/prompts/template.py,sha256=
|
|
28
|
+
trodo/prompts/compile.py,sha256=XoKDKz6Yaofhrk-87AVW-SnwbiXLvfp161AL2bAFzNY,8802
|
|
29
|
+
trodo/prompts/template.py,sha256=eGO2ZVNOw6JK7Ffj1UCYDMJFYyX5qQW2POlmFJSQpAo,9975
|
|
30
30
|
trodo/prompts/types.py,sha256=OYpaLi1H9pJjeq0gsJo8eb-bWoF8rw2u_7pdukudtqo,5258
|
|
31
31
|
trodo/queue/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
32
|
trodo/queue/batch_flusher.py,sha256=4Lg6T3Urwi9U0Q4FpFGPmjDYKg4ZliCTR-ND8BJvWaY,1298
|
|
@@ -36,7 +36,7 @@ trodo/session/server_session.py,sha256=McsudEiq33XDq3nqxgzBcUvIjQxCMscwEuAPnYXrT
|
|
|
36
36
|
trodo/session/session_manager.py,sha256=7ht5LeeZX1HLLfPeNV_a8NkXGbHl3up0CRtCGr3EzjQ,2995
|
|
37
37
|
trodo/util/__init__.py,sha256=Z9c4rPPdKg06Kk3byKheDqksSgR4WNvS475oQ5sNljc,54
|
|
38
38
|
trodo/util/lru.py,sha256=QIsM7s6J_E9ZjiXatSyReZIEeGypTAcgrPWWy8YsRa4,2465
|
|
39
|
-
trodo_python-2.
|
|
40
|
-
trodo_python-2.
|
|
41
|
-
trodo_python-2.
|
|
42
|
-
trodo_python-2.
|
|
39
|
+
trodo_python-2.23.1.dist-info/METADATA,sha256=WDaY32vkP94_NzHjeE38-nnxCaUS3bGRYa5YgCA3qYI,25308
|
|
40
|
+
trodo_python-2.23.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
41
|
+
trodo_python-2.23.1.dist-info/top_level.txt,sha256=VCQu1CJWFmNsqTs1YxMcw4Mq35Tc3z3uI9RwHEXAayQ,6
|
|
42
|
+
trodo_python-2.23.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|