11from __future__ import annotations
22
3+ import json
34import uuid
45from typing import Any
56from datetime import UTC , datetime
1617from agentex .lib .core .tracing .trace import Trace , AsyncTrace
1718from agentex .lib .core .tracing .span_error import (
1819 SPAN_ERROR_KEY ,
20+ ERROR_CLASSIFIER_VERSION ,
1921 PlatformError ,
2022 ApplicationError ,
2123 CategorizedError ,
@@ -36,6 +38,12 @@ def _make_span(data=None) -> Span:
3638 )
3739
3840
41+ def _synthetic_function (module_name : str , filename : str , body : str , ** values : Any ) -> Any :
42+ namespace = {"__name__" : module_name , ** values }
43+ exec (compile (f"def run():\n { body } \n " , filename , "exec" ), namespace )
44+ return namespace ["run" ]
45+
46+
3947# ---------------------------------------------------------------------------
4048# Helpers: set_span_error / get_span_error
4149# ---------------------------------------------------------------------------
@@ -54,13 +62,12 @@ def test_set_then_get_on_none_data(self):
5462 "type" : "ValueError" ,
5563 "message" : "boom" ,
5664 "category" : "unknown" ,
65+ "category_source" : "fallback" ,
66+ "classifier_version" : ERROR_CLASSIFIER_VERSION ,
67+ "category_reason" : "stack_no_traceback" ,
5768 }
5869 assert isinstance (span .data , dict )
59- assert span .data [SPAN_ERROR_KEY ] == {
60- "type" : "ValueError" ,
61- "message" : "boom" ,
62- "category" : "unknown" ,
63- }
70+ assert span .data [SPAN_ERROR_KEY ] == get_span_error (span )
6471
6572 def test_set_uses_explicit_exception_category (self ):
6673 span = _make_span (data = None )
@@ -69,12 +76,18 @@ def test_set_uses_explicit_exception_category(self):
6976 "type" : "PlatformError" ,
7077 "message" : "unavailable" ,
7178 "category" : "platform" ,
79+ "category_source" : "categorized_error" ,
80+ "classifier_version" : ERROR_CLASSIFIER_VERSION ,
81+ "category_reason" : "canonical_categorized_error" ,
7282 }
7383
7484 def test_explicit_category_takes_precedence (self ):
7585 span = _make_span (data = None )
7686 set_span_error (span , PlatformError ("bad input" ), error_category = "application" )
77- assert get_span_error (span )["category" ] == "application" # type: ignore[index]
87+ error = get_span_error (span )
88+ assert error is not None
89+ assert error ["category" ] == "application"
90+ assert error ["category_source" ] == "explicit"
7891
7992 def test_set_uses_application_error_category (self ):
8093 span = _make_span (data = None )
@@ -89,6 +102,54 @@ class ImplicitlyCategorizedError(RuntimeError):
89102 set_span_error (span , ImplicitlyCategorizedError ("boom" ))
90103 assert get_span_error (span )["category" ] == "unknown" # type: ignore[index]
91104
105+ def test_agentex_internal_origin_is_platform (self ):
106+ platform = _synthetic_function (
107+ "agentex.lib.synthetic_runtime" ,
108+ "/synthetic/agentex/runtime.py" ,
109+ "raise RuntimeError('boom')" ,
110+ )
111+ span = _make_span ()
112+ try :
113+ platform ()
114+ except Exception as exc :
115+ set_span_error (span , exc )
116+
117+ error = get_span_error (span )
118+ assert error is not None
119+ assert error ["category" ] == "platform"
120+ assert error ["category_source" ] == "stack_trace"
121+ assert error ["category_reason" ] == "stack_rule:platform_module"
122+
123+ def test_stdlib_dependency_under_application_is_application (self ):
124+ application = _synthetic_function (
125+ "customer_agent.main" ,
126+ "/synthetic/application/main.py" ,
127+ "parse('{')" ,
128+ parse = json .loads ,
129+ )
130+ span = _make_span ()
131+ try :
132+ application ()
133+ except Exception as exc :
134+ set_span_error (span , exc )
135+
136+ assert get_span_error (span )["category" ] == "application" # type: ignore[index]
137+
138+ def test_stdlib_dependency_under_agentex_is_platform (self ):
139+ platform = _synthetic_function (
140+ "agentex.lib.synthetic_runtime" ,
141+ "/synthetic/agentex/runtime.py" ,
142+ "parse('{')" ,
143+ parse = json .loads ,
144+ )
145+ span = _make_span ()
146+ try :
147+ platform ()
148+ except Exception as exc :
149+ set_span_error (span , exc )
150+
151+ assert get_span_error (span )["category" ] == "platform" # type: ignore[index]
152+
92153 def test_set_preserves_existing_dict_keys (self ):
93154 span = _make_span (data = {"__span_type__" : "LLM" })
94155 set_span_error (span , RuntimeError ("nope" ))
@@ -127,7 +188,10 @@ def test_sync_span_records_error_and_reraises(self):
127188 assert err == {
128189 "type" : "ValueError" ,
129190 "message" : "boom" ,
130- "category" : "unknown" ,
191+ "category" : "application" ,
192+ "category_source" : "stack_trace" ,
193+ "classifier_version" : ERROR_CLASSIFIER_VERSION ,
194+ "category_reason" : "stack_rule:unowned_absolute_source" ,
131195 }
132196
133197 def test_sync_span_success_has_no_error (self ):
@@ -148,7 +212,10 @@ async def test_async_span_records_error_and_reraises(self):
148212 assert err == {
149213 "type" : "RuntimeError" ,
150214 "message" : "kaboom" ,
151- "category" : "unknown" ,
215+ "category" : "application" ,
216+ "category_source" : "stack_trace" ,
217+ "classifier_version" : ERROR_CLASSIFIER_VERSION ,
218+ "category_reason" : "stack_rule:unowned_absolute_source" ,
152219 }
153220
154221
@@ -193,6 +260,9 @@ def test_error_maps_to_status_error(self):
193260 "type" : "ValueError" ,
194261 "message" : "boom" ,
195262 "category" : "application" ,
263+ "category_source" : "stack_trace" ,
264+ "classifier_version" : ERROR_CLASSIFIER_VERSION ,
265+ "category_reason" : "stack_rule:application_module" ,
196266 }
197267 }
198268 )
@@ -204,6 +274,9 @@ def test_error_maps_to_status_error(self):
204274 assert sgp_span .metadata ["error_type" ] == "ValueError"
205275 assert sgp_span .metadata ["error_message" ] == "boom"
206276 assert sgp_span .metadata ["error_category" ] == "application"
277+ assert sgp_span .metadata ["error_category_source" ] == "stack_trace"
278+ assert sgp_span .metadata ["error_classifier_version" ] == ERROR_CLASSIFIER_VERSION
279+ assert sgp_span .metadata ["error_category_reason" ] == "stack_rule:application_module"
207280
208281 def test_no_error_leaves_status_success (self ):
209282 from agentex .lib .core .tracing .processors .sgp_tracing_processor import _build_sgp_span
0 commit comments