|
137 | 137 | # across sibling parallel-branches branches (pipeline-utilities §11); |
138 | 138 | # without it the two inner ``ask`` nodes of two branches with the |
139 | 139 | # same namespace + fan_out_index would collide on the same key. |
140 | | -_StackKey = tuple[tuple[str, ...], int, int | None, str | None] |
| 140 | +# Proposal 0084 (spec v0.81.0): a node span is keyed by the innermost scalars |
| 141 | +# AND the full enclosing fan-out / branch lineage chains. The chains are the |
| 142 | +# addition: an inner node under two concurrent outer fan-out instances shares |
| 143 | +# the same innermost scalar across the outer instances, so a scalar-only key |
| 144 | +# would collide (last-writer-wins drops / mis-closes the second) and the |
| 145 | +# LLM-parent exact-match would resolve to a sibling's span. The scalars are |
| 146 | +# RETAINED (not replaced by the chains) because ``_key_for`` is also used to |
| 147 | +# LOOK UP a callable-parallel-branch event in ``open_spans`` on the publish |
| 148 | +# path (``_publish_active_span``): a callable branch carries its branch_name on |
| 149 | +# the event but never extends branch_name_chain (no subgraph descent), so on |
| 150 | +# the chains alone its key would EQUAL its own parallel-branches NODE's key -- |
| 151 | +# the lookup would return that NODE instead of missing, and the branch's |
| 152 | +# per-branch dispatch span would never be synthesized (verified: blanking the |
| 153 | +# scalars drops the dispatch span). The scalar branch_name (key[3]) keeps the |
| 154 | +# two distinct. Past that lookup the scalars are redundant with the chains for |
| 155 | +# storage uniqueness (each equals its chain's innermost non-None entry); the |
| 156 | +# merged spec keys the driving span chains-only, so the retained scalar is a |
| 157 | +# harmless superset kept for the callable-branch lookup. The common |
| 158 | +# single-level case keys as before plus the (empty / length-1) chains. |
| 159 | +_StackKey = tuple[ |
| 160 | + tuple[str, ...], int, int | None, str | None, tuple[int | None, ...], tuple[str | None, ...] |
| 161 | +] |
141 | 162 |
|
142 | 163 | # Lineage-aware dispatch keys (proposal 0045): the fan-out / pb NODE namespace |
143 | 164 | # prefix plus the fan-out instance index / branch name chain slices along the |
@@ -1304,7 +1325,7 @@ def _collect_augmentation_targets( |
1304 | 1325 | # identified structurally by their presence in the |
1305 | 1326 | # parent_node_name caches) |
1306 | 1327 | for key, open_span in inv_state.open_spans.items(): |
1307 | | - ns, _ai, _fi, _bn = key |
| 1328 | + ns = key[0] |
1308 | 1329 | if ns != aug_ns and not is_strict_prefix(ns, aug_ns): |
1309 | 1330 | continue |
1310 | 1331 | # Shared-parent check: a fan-out NODE or parallel-branches NODE |
@@ -1587,6 +1608,8 @@ def _handle_typed_llm_retry_attempt(self, event: LlmRetryAttemptEvent) -> None: |
1587 | 1608 | calling_attempt_index=event.attempt_index, |
1588 | 1609 | calling_fan_out_index=event.fan_out_index, |
1589 | 1610 | calling_branch_name=event.branch_name, |
| 1611 | + calling_fan_out_index_chain=event.fan_out_index_chain, |
| 1612 | + calling_branch_name_chain=event.branch_name_chain, |
1590 | 1613 | ) |
1591 | 1614 | attrs: dict[str, Any] = { |
1592 | 1615 | "openarmature.llm.model": event.model, |
@@ -1808,6 +1831,8 @@ def _handle_tool_call(self, event: ToolCallEvent | ToolCallFailedEvent) -> None: |
1808 | 1831 | calling_attempt_index=event.attempt_index, |
1809 | 1832 | calling_fan_out_index=event.fan_out_index, |
1810 | 1833 | calling_branch_name=event.branch_name, |
| 1834 | + calling_fan_out_index_chain=event.fan_out_index_chain, |
| 1835 | + calling_branch_name_chain=event.branch_name_chain, |
1811 | 1836 | ) |
1812 | 1837 | attrs: dict[str, Any] = {"openarmature.tool.name": event.tool_name} |
1813 | 1838 | if event.tool_call_id is not None: |
@@ -1926,6 +1951,8 @@ def _handle_embedding(self, event: EmbeddingEvent | EmbeddingFailedEvent) -> Non |
1926 | 1951 | calling_attempt_index=event.attempt_index, |
1927 | 1952 | calling_fan_out_index=event.fan_out_index, |
1928 | 1953 | calling_branch_name=event.branch_name, |
| 1954 | + calling_fan_out_index_chain=event.fan_out_index_chain, |
| 1955 | + calling_branch_name_chain=event.branch_name_chain, |
1929 | 1956 | ) |
1930 | 1957 | attrs: dict[str, Any] = {} |
1931 | 1958 | cid = current_correlation_id() |
@@ -2036,6 +2063,8 @@ def _handle_rerank(self, event: RerankEvent | RerankFailedEvent) -> None: |
2036 | 2063 | calling_attempt_index=event.attempt_index, |
2037 | 2064 | calling_fan_out_index=event.fan_out_index, |
2038 | 2065 | calling_branch_name=event.branch_name, |
| 2066 | + calling_fan_out_index_chain=event.fan_out_index_chain, |
| 2067 | + calling_branch_name_chain=event.branch_name_chain, |
2039 | 2068 | ) |
2040 | 2069 | attrs: dict[str, Any] = {} |
2041 | 2070 | cid = current_correlation_id() |
@@ -2147,6 +2176,8 @@ def _handle_failure_isolated(self, event: FailureIsolatedEvent) -> None: |
2147 | 2176 | calling_attempt_index=event.attempt_index, |
2148 | 2177 | calling_fan_out_index=event.fan_out_index, |
2149 | 2178 | calling_branch_name=event.branch_name, |
| 2179 | + calling_fan_out_index_chain=event.fan_out_index_chain, |
| 2180 | + calling_branch_name_chain=event.branch_name_chain, |
2150 | 2181 | ) |
2151 | 2182 | attrs: dict[str, Any] = { |
2152 | 2183 | "openarmature.failure_isolation.event_name": event.event_name, |
@@ -2182,16 +2213,25 @@ def _resolve_llm_parent( |
2182 | 2213 | calling_attempt_index: int, |
2183 | 2214 | calling_fan_out_index: int | None, |
2184 | 2215 | calling_branch_name: str | None, |
| 2216 | + calling_fan_out_index_chain: tuple[int | None, ...], |
| 2217 | + calling_branch_name_chain: tuple[str | None, ...], |
2185 | 2218 | ) -> object: |
2186 | 2219 | """Look up the calling node's span using the calling-node |
2187 | 2220 | identity, falling back through the per-instance fan-out dispatch |
2188 | 2221 | span, subgraph dispatch / detached root, and the invocation span.""" |
2189 | | - # 1. Direct match on the calling node's ``_StackKey``. |
| 2222 | + # 1. Direct match on the calling node's ``_StackKey`` -- the |
| 2223 | + # lineage-disambiguated calling-node span (proposal 0084 §5.5 |
| 2224 | + # "Lineage-resolved parent"). Keyed by the full enclosing fan-out / |
| 2225 | + # branch chain so a nested provider call parents under ITS OWN |
| 2226 | + # calling node's span, not a sibling in another concurrent outer |
| 2227 | + # instance that shares the innermost scalar fan_out_index. |
2190 | 2228 | calling_key: _StackKey = ( |
2191 | 2229 | calling_namespace_prefix, |
2192 | 2230 | calling_attempt_index, |
2193 | 2231 | calling_fan_out_index, |
2194 | 2232 | calling_branch_name, |
| 2233 | + calling_fan_out_index_chain, |
| 2234 | + calling_branch_name_chain, |
2195 | 2235 | ) |
2196 | 2236 | calling = inv_state.open_spans.get(calling_key) |
2197 | 2237 | if calling is not None: |
@@ -2264,7 +2304,17 @@ def _open_invocation_span( |
2264 | 2304 | self._invocation_span[invocation_id] = _OpenSpan(span=span) |
2265 | 2305 |
|
2266 | 2306 | def _key_for(self, event: NodeEvent) -> _StackKey: |
2267 | | - return (event.namespace, event.attempt_index, event.fan_out_index, event.branch_name) |
| 2307 | + # Proposal 0084: scalars (retained) + enclosing lineage chains (added), |
| 2308 | + # so concurrent nested fan-out node spans don't collide while a callable |
| 2309 | + # parallel-branch still stays distinct from its parallel-branches node. |
| 2310 | + return ( |
| 2311 | + event.namespace, |
| 2312 | + event.attempt_index, |
| 2313 | + event.fan_out_index, |
| 2314 | + event.branch_name, |
| 2315 | + event.fan_out_index_chain, |
| 2316 | + event.branch_name_chain, |
| 2317 | + ) |
2268 | 2318 |
|
2269 | 2319 | def _resolve_parent_context( |
2270 | 2320 | self, |
@@ -2955,23 +3005,21 @@ def _find_fan_out_node_span( |
2955 | 3005 | self, inv_state: _InvState, prefix: tuple[str, ...], event: NodeEvent |
2956 | 3006 | ) -> _OpenSpan | None: |
2957 | 3007 | """Find the currently-open fan-out / pb NODE span at ``prefix`` on the |
2958 | | - given event's ENCLOSING lineage. When the NODE is itself nested inside an |
2959 | | - outer fan-out instance / branch, several instances of the same NODE |
2960 | | - namespace are open at once under concurrency, so a namespace-only scan |
2961 | | - would bind the wrong one. The NODE's own event carries the instance / |
2962 | | - branch it sits in as its fan_out_index / branch_name (key[2] / key[3]); |
2963 | | - that equals the synthesizing event's chain entry at the level above this |
2964 | | - NODE.""" |
2965 | | - n = len(prefix) |
2966 | | - enclosing_fi = ( |
2967 | | - event.fan_out_index_chain[n - 2] if n >= 2 and n - 2 < len(event.fan_out_index_chain) else None |
2968 | | - ) |
2969 | | - enclosing_bn = ( |
2970 | | - event.branch_name_chain[n - 2] if n >= 2 and n - 2 < len(event.branch_name_chain) else None |
2971 | | - ) |
| 3008 | + given event's ENCLOSING lineage. When the NODE is itself nested inside |
| 3009 | + one or more outer fan-out instances / branches, several instances of the |
| 3010 | + same NODE namespace are open at once under concurrency, so a |
| 3011 | + namespace-only scan would bind the wrong one. Disambiguate by the full |
| 3012 | + enclosing chain (proposal 0084): the NODE span sits on the synthesizing |
| 3013 | + event's call-stack ancestor path iff its stored lineage chain is a |
| 3014 | + prefix of the event's -- the same lineage-boundary rule the augmentation |
| 3015 | + scoping uses (``_span_chain_on_path``). Matching the innermost scalar |
| 3016 | + alone is ambiguous at >=3 levels, where an intermediate instance index |
| 3017 | + repeats across concurrent outer instances (both would match the scalar |
| 3018 | + but only one is on the event's path).""" |
2972 | 3019 | for key, open_span in inv_state.open_spans.items(): |
2973 | | - ns, _attempt, fan_idx, bn = key |
2974 | | - if ns == prefix and fan_idx == enclosing_fi and bn == enclosing_bn: |
| 3020 | + if key[0] == prefix and _span_chain_on_path( |
| 3021 | + open_span, event.fan_out_index_chain, event.branch_name_chain |
| 3022 | + ): |
2975 | 3023 | return open_span |
2976 | 3024 | return None |
2977 | 3025 |
|
|
0 commit comments