struct-sdk 0.2.13__tar.gz → 0.2.15__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.
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/PKG-INFO +1 -1
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/pyproject.toml +1 -1
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/src/struct_sdk/core.py +23 -39
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/src/struct_sdk/langchain.py +19 -4
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/.gitignore +0 -0
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/LICENSE +0 -0
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/README.md +0 -0
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/src/struct_sdk/__init__.py +0 -0
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/src/struct_sdk/_genai_content.py +0 -0
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/src/struct_sdk/anthropic.py +0 -0
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/src/struct_sdk/claude_agent.py +0 -0
- {struct_sdk-0.2.13 → struct_sdk-0.2.15}/src/struct_sdk/openai.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: struct-sdk
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.15
|
|
4
4
|
Summary: Struct agent observability SDK — auto-instruments AI agent frameworks with OpenTelemetry
|
|
5
5
|
Project-URL: Homepage, https://struct.ai
|
|
6
6
|
Project-URL: Documentation, https://struct.ai/docs
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "struct-sdk"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.15"
|
|
8
8
|
description = "Struct agent observability SDK — auto-instruments AI agent frameworks with OpenTelemetry"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -161,7 +161,8 @@ def _attach_current_span(
|
|
|
161
161
|
"""Make ``span`` the current OTel span so children — customer-created raw spans
|
|
162
162
|
AND nested SDK spans — nest under it (ambient-context honoring, good-citizen
|
|
163
163
|
behavior). Returns ``(token, enter_task)`` for a later task-safe detach."""
|
|
164
|
-
|
|
164
|
+
ctx = trace.set_span_in_context(span)
|
|
165
|
+
token = _otel_context.attach(ctx)
|
|
165
166
|
return token, _current_task_or_none()
|
|
166
167
|
|
|
167
168
|
|
|
@@ -657,51 +658,34 @@ class _AgentContext:
|
|
|
657
658
|
and enclosing_agent_span is not None
|
|
658
659
|
)
|
|
659
660
|
|
|
660
|
-
# ──
|
|
661
|
-
# A
|
|
662
|
-
#
|
|
663
|
-
#
|
|
664
|
-
#
|
|
665
|
-
#
|
|
666
|
-
#
|
|
667
|
-
#
|
|
661
|
+
# ── Parentage: PURE NEST (Option D) ──────────────────────────────
|
|
662
|
+
# A non-break-out agent nests under whatever OTel span is active — the
|
|
663
|
+
# host's ambient context — exactly like every peer LLM/agent SDK
|
|
664
|
+
# (Vercel, Pydantic AI, OpenLLMetry, OpenInference, Langfuse). We do NOT
|
|
665
|
+
# second-guess the active span or unilaterally re-root: the old
|
|
666
|
+
# `foreign_root` heuristic detached from ANY active span and so ripped
|
|
667
|
+
# agents out of legitimate host request traces. Cross-conversation
|
|
668
|
+
# grouping is carried by gen_ai.conversation.id, never by trace
|
|
669
|
+
# parentage.
|
|
668
670
|
#
|
|
669
|
-
#
|
|
670
|
-
#
|
|
671
|
-
#
|
|
672
|
-
#
|
|
673
|
-
#
|
|
674
|
-
#
|
|
675
|
-
#
|
|
676
|
-
# the contextvar is live in the same task), so they fall through to the
|
|
677
|
-
# inherit path below and stay nested in the same trace.
|
|
678
|
-
active_span_context = trace.get_current_span().get_span_context()
|
|
679
|
-
foreign_root = (
|
|
680
|
-
not break_out
|
|
681
|
-
and enclosing_session_id is None
|
|
682
|
-
and active_span_context.is_valid
|
|
683
|
-
)
|
|
684
|
-
|
|
671
|
+
# If a runtime propagates a stale/unwanted span across a boundary — e.g.
|
|
672
|
+
# our own persistent_agent's long-lived Temporal workflow span — it is
|
|
673
|
+
# cleared at the SOURCE (edgedive-server resets the OTel context at the
|
|
674
|
+
# Temporal activity boundary so no foreign span is active here), never
|
|
675
|
+
# defended against in the SDK. (A provenance-gated detach — re-root only
|
|
676
|
+
# a leaked Struct-OWNED span, nest under host spans — is a documented
|
|
677
|
+
# follow-up, deferred until a real customer needs it.)
|
|
685
678
|
if break_out:
|
|
686
|
-
#
|
|
687
|
-
# a
|
|
688
|
-
#
|
|
679
|
+
# Explicit different session while nested under another Struct agent:
|
|
680
|
+
# start a fresh root trace and carry a spawned-by Link to the
|
|
681
|
+
# enclosing agent. This is the one deliberate re-root, and it is
|
|
682
|
+
# opt-in (the caller passed an explicit, differing session_id).
|
|
689
683
|
assert enclosing_agent_span is not None # narrowing for mypy
|
|
690
|
-
links = [trace.Link(enclosing_agent_span.get_span_context())]
|
|
691
|
-
self._span = tracer.start_span(
|
|
692
|
-
f"invoke_agent {agent_name}",
|
|
693
|
-
kind=trace.SpanKind.INTERNAL,
|
|
694
|
-
context=_OtelContext(), # empty context → new root trace
|
|
695
|
-
links=links,
|
|
696
|
-
)
|
|
697
|
-
elif foreign_root:
|
|
698
|
-
# New root trace; the leaked/foreign active span becomes a Link
|
|
699
|
-
# (causal origin), NOT this turn's parent.
|
|
700
684
|
self._span = tracer.start_span(
|
|
701
685
|
f"invoke_agent {agent_name}",
|
|
702
686
|
kind=trace.SpanKind.INTERNAL,
|
|
703
687
|
context=_OtelContext(), # empty context → new root trace
|
|
704
|
-
links=[trace.Link(
|
|
688
|
+
links=[trace.Link(enclosing_agent_span.get_span_context())],
|
|
705
689
|
)
|
|
706
690
|
else:
|
|
707
691
|
self._span = tracer.start_span(
|
|
@@ -104,10 +104,25 @@ def patch(sdk: StructSDK) -> None:
|
|
|
104
104
|
*args: Any,
|
|
105
105
|
**kwargs: Any,
|
|
106
106
|
) -> Any:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
# Our handler injection/stripping must NEVER break the host's
|
|
108
|
+
# configure(). It runs on the invoke/stream/batch path and,
|
|
109
|
+
# unlike the on_* callbacks, has no ``raise_error=False`` net.
|
|
110
|
+
# _inject_handler / _strip_struct read ``h.name`` / ``.handlers``
|
|
111
|
+
# on host-owned callback objects via ``getattr(..., default)``,
|
|
112
|
+
# which swallows only AttributeError — a getter raising anything
|
|
113
|
+
# else (hostile/lazy/proxy handler) would abort a host call that
|
|
114
|
+
# LangChain's own configure (which never reads ``.name``) would
|
|
115
|
+
# have completed. Degrade ATOMICALLY: on any failure fall through
|
|
116
|
+
# with the ORIGINAL args (both rebind only if both succeed).
|
|
117
|
+
# Mirrors langchain.ts's resolveConfigureHandlerArgs guard.
|
|
118
|
+
try:
|
|
119
|
+
new_inheritable = _inject_handler(
|
|
120
|
+
inheritable_callbacks, _active_handler
|
|
121
|
+
)
|
|
122
|
+
new_local = _strip_struct(local_callbacks)
|
|
123
|
+
inheritable_callbacks, local_callbacks = new_inheritable, new_local
|
|
124
|
+
except Exception: # noqa: BLE001 — never break the host's configure()
|
|
125
|
+
pass
|
|
111
126
|
return orig_func(
|
|
112
127
|
cls,
|
|
113
128
|
inheritable_callbacks,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|