openinference-instrumentation-beeai 0.1.5__py3-none-any.whl → 0.1.7__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.
- openinference/instrumentation/beeai/__init__.py +78 -104
- openinference/instrumentation/beeai/_span.py +81 -0
- openinference/instrumentation/beeai/_utils.py +75 -0
- openinference/instrumentation/beeai/processors/__init__.py +0 -0
- openinference/instrumentation/beeai/processors/agents/__init__.py +0 -0
- openinference/instrumentation/beeai/processors/agents/base.py +34 -0
- openinference/instrumentation/beeai/processors/agents/react.py +77 -0
- openinference/instrumentation/beeai/processors/agents/requirement_agent.py +71 -0
- openinference/instrumentation/beeai/processors/agents/tool_calling.py +34 -0
- openinference/instrumentation/beeai/processors/base.py +60 -0
- openinference/instrumentation/beeai/processors/chat.py +239 -0
- openinference/instrumentation/beeai/processors/embedding.py +71 -0
- openinference/instrumentation/beeai/processors/locator.py +106 -0
- openinference/instrumentation/beeai/processors/requirement.py +67 -0
- openinference/instrumentation/beeai/processors/tool.py +72 -0
- openinference/instrumentation/beeai/processors/workflow.py +108 -0
- openinference/instrumentation/beeai/version.py +1 -1
- {openinference_instrumentation_beeai-0.1.5.dist-info → openinference_instrumentation_beeai-0.1.7.dist-info}/METADATA +12 -9
- openinference_instrumentation_beeai-0.1.7.dist-info/RECORD +21 -0
- openinference/instrumentation/beeai/middleware.py +0 -291
- openinference/instrumentation/beeai/utils/build_trace_tree.py +0 -170
- openinference/instrumentation/beeai/utils/create_span.py +0 -80
- openinference/instrumentation/beeai/utils/get_serialized_object_safe.py +0 -302
- openinference/instrumentation/beeai/utils/id_name_manager.py +0 -58
- openinference_instrumentation_beeai-0.1.5.dist-info/RECORD +0 -11
- {openinference_instrumentation_beeai-0.1.5.dist-info → openinference_instrumentation_beeai-0.1.7.dist-info}/WHEEL +0 -0
- {openinference_instrumentation_beeai-0.1.5.dist-info → openinference_instrumentation_beeai-0.1.7.dist-info}/entry_points.txt +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openinference-instrumentation-beeai
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: OpenInference BeeAI Instrumentation
|
|
5
5
|
Project-URL: Homepage, https://github.com/Arize-ai/openinference/tree/main/python/instrumentation/openinference-instrumentation-beeai
|
|
6
|
-
Author
|
|
6
|
+
Author: IBM Corp.
|
|
7
|
+
Maintainer-email: Tomas Dvorak <toomas2d@gmail.com>
|
|
7
8
|
License-Expression: Apache-2.0
|
|
8
9
|
Classifier: Development Status :: 5 - Production/Stable
|
|
9
10
|
Classifier: Intended Audience :: Developers
|
|
@@ -14,16 +15,18 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
17
|
Requires-Python: <3.14,>=3.11
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist: openinference-
|
|
19
|
-
Requires-Dist:
|
|
20
|
-
Requires-Dist: opentelemetry-
|
|
21
|
-
Requires-Dist: opentelemetry-
|
|
18
|
+
Requires-Dist: beeai-framework<0.2.0,>=0.1.32
|
|
19
|
+
Requires-Dist: openinference-instrumentation>=0.1.36
|
|
20
|
+
Requires-Dist: openinference-semantic-conventions>=0.1.21
|
|
21
|
+
Requires-Dist: opentelemetry-api>=1.36.0
|
|
22
|
+
Requires-Dist: opentelemetry-instrumentation>=0.57b0
|
|
23
|
+
Requires-Dist: opentelemetry-semantic-conventions>=0.57b0
|
|
22
24
|
Requires-Dist: wrapt>=1.17.2
|
|
23
25
|
Provides-Extra: instruments
|
|
24
|
-
Requires-Dist: beeai-framework
|
|
26
|
+
Requires-Dist: beeai-framework>=0.1.32; extra == 'instruments'
|
|
25
27
|
Provides-Extra: test
|
|
26
|
-
Requires-Dist: beeai-framework
|
|
28
|
+
Requires-Dist: beeai-framework>=0.1.32; extra == 'test'
|
|
29
|
+
Requires-Dist: opentelemetry-exporter-otlp; extra == 'test'
|
|
27
30
|
Requires-Dist: opentelemetry-sdk; extra == 'test'
|
|
28
31
|
Description-Content-Type: text/markdown
|
|
29
32
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
openinference/instrumentation/beeai/__init__.py,sha256=5_bUn-XMonUXUgbbqU_PrGyW2ojiWQD7LUvjB7B-g0A,4467
|
|
2
|
+
openinference/instrumentation/beeai/_span.py,sha256=VZfoQHDUlAeyHxI7MnQ6QGq2LwhHiI1AQ2Uw_4Dn5Zw,2750
|
|
3
|
+
openinference/instrumentation/beeai/_utils.py,sha256=nZQkVv1QTNVYu23EqEaRkYtncmpE4LLqEH_xCoHzM3c,2247
|
|
4
|
+
openinference/instrumentation/beeai/version.py,sha256=YpKDcdV7CqL8n45u267wKtyloM13FSVbOdrqgNZnSLM,22
|
|
5
|
+
openinference/instrumentation/beeai/processors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
openinference/instrumentation/beeai/processors/base.py,sha256=akaQCsZmbapEC5tctD_VXWiBOu2pYHFRJRZ_pynpApc,2176
|
|
7
|
+
openinference/instrumentation/beeai/processors/chat.py,sha256=yyNjoG3AY2XPNHr6t2JJEwmx5NCibc3ThsWBL8lIuTU,9082
|
|
8
|
+
openinference/instrumentation/beeai/processors/embedding.py,sha256=T9fZs2M7qEs4SnLYbSXRbhe3P7rCNch-snRQBDSC9Es,2598
|
|
9
|
+
openinference/instrumentation/beeai/processors/locator.py,sha256=G9TFW_HgXM1TrOVdvtRU2Eq3D-atHLAET4fSo4F02X8,3635
|
|
10
|
+
openinference/instrumentation/beeai/processors/requirement.py,sha256=Q9DgHDd-5rmP88Fe00d7cNKQg5zQ7ILuRzVAej5xfmk,2666
|
|
11
|
+
openinference/instrumentation/beeai/processors/tool.py,sha256=LmYD9Oj9l_jRZEXoTyfMSykZQum0EHdBZOW6AtiPoHE,2720
|
|
12
|
+
openinference/instrumentation/beeai/processors/workflow.py,sha256=OMwFFHv3mp4M4hFvH7utYd_fiSkTnBcl2oUVaMEdy-A,3815
|
|
13
|
+
openinference/instrumentation/beeai/processors/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
openinference/instrumentation/beeai/processors/agents/base.py,sha256=3fidrUoU9pVixq9YN_y1jkNMozNMX-YG2CMuh855cLk,1244
|
|
15
|
+
openinference/instrumentation/beeai/processors/agents/react.py,sha256=rS3xlvgyZ5G6MyDMeSh4xFLT_66h7GVAYEYlwZCpIdY,3022
|
|
16
|
+
openinference/instrumentation/beeai/processors/agents/requirement_agent.py,sha256=HpleY8pNWojuUqcae2PZgat7Xq2edU9C-uz0YjZQUyc,2774
|
|
17
|
+
openinference/instrumentation/beeai/processors/agents/tool_calling.py,sha256=yaWP5JmGuvZIha9iUSKgv0MJgI0QSbuiJLLQFnbqUZw,1223
|
|
18
|
+
openinference_instrumentation_beeai-0.1.7.dist-info/METADATA,sha256=j0D1RjhUiFGl8lZ8vzgQzt_yGWaZO1-2eQVDe6maZdI,5491
|
|
19
|
+
openinference_instrumentation_beeai-0.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
20
|
+
openinference_instrumentation_beeai-0.1.7.dist-info/entry_points.txt,sha256=ee7EUhbWv-XK1dxhPXuFVy9qstzj-lc-265Phe2Ml9s,183
|
|
21
|
+
openinference_instrumentation_beeai-0.1.7.dist-info/RECORD,,
|
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 IBM Corp.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
import logging
|
|
16
|
-
import time
|
|
17
|
-
from datetime import datetime, timezone
|
|
18
|
-
from importlib.metadata import PackageNotFoundError, version
|
|
19
|
-
from typing import Any, Callable, Dict, Optional, cast
|
|
20
|
-
|
|
21
|
-
from beeai_framework.agents.base import BaseAgent
|
|
22
|
-
from beeai_framework.agents.react.agent import ReActAgent
|
|
23
|
-
from beeai_framework.agents.react.events import ReActAgentSuccessEvent
|
|
24
|
-
from beeai_framework.agents.tool_calling.agent import ToolCallingAgent
|
|
25
|
-
from beeai_framework.agents.tool_calling.events import ToolCallingAgentSuccessEvent
|
|
26
|
-
from beeai_framework.backend import Role
|
|
27
|
-
from beeai_framework.context import RunContext
|
|
28
|
-
from beeai_framework.emitter import EventMeta
|
|
29
|
-
from beeai_framework.errors import FrameworkError
|
|
30
|
-
|
|
31
|
-
from openinference.instrumentation import OITracer
|
|
32
|
-
from openinference.semconv.trace import OpenInferenceSpanKindValues, SpanAttributes
|
|
33
|
-
|
|
34
|
-
from .utils.build_trace_tree import build_trace_tree
|
|
35
|
-
from .utils.create_span import FrameworkSpan, create_span
|
|
36
|
-
from .utils.get_serialized_object_safe import get_serialized_object_safe
|
|
37
|
-
from .utils.id_name_manager import IdNameManager
|
|
38
|
-
|
|
39
|
-
try:
|
|
40
|
-
Version = version("beeai-framework")
|
|
41
|
-
except PackageNotFoundError:
|
|
42
|
-
Version = "unknown"
|
|
43
|
-
|
|
44
|
-
id_name_manager = IdNameManager()
|
|
45
|
-
active_traces_map: Dict[str, str] = {}
|
|
46
|
-
|
|
47
|
-
logger = logging.getLogger(__name__)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def create_telemetry_middleware(
|
|
51
|
-
tracer: OITracer, main_span_kind: str
|
|
52
|
-
) -> Callable[[RunContext], None]:
|
|
53
|
-
def middleware(context: RunContext) -> None:
|
|
54
|
-
trace_obj = getattr(context.emitter, "trace", None)
|
|
55
|
-
trace_id = getattr(trace_obj, "id", None)
|
|
56
|
-
if not trace_id:
|
|
57
|
-
raise FrameworkError("Fatal error. Missing traceId", context=context.__dict__)
|
|
58
|
-
|
|
59
|
-
emitter = context.emitter
|
|
60
|
-
run_params = context.run_params
|
|
61
|
-
instance = context.instance
|
|
62
|
-
|
|
63
|
-
if trace_id in active_traces_map:
|
|
64
|
-
return
|
|
65
|
-
active_traces_map[trace_id] = type(instance).__name__
|
|
66
|
-
base_path = ".".join(emitter.namespace)
|
|
67
|
-
prompt = None
|
|
68
|
-
if isinstance(instance, BaseAgent):
|
|
69
|
-
prompt = run_params.get("prompt") if isinstance(run_params, dict) else None
|
|
70
|
-
|
|
71
|
-
spans_map: Dict[str, FrameworkSpan] = {}
|
|
72
|
-
parent_ids_map: dict[str, int] = {}
|
|
73
|
-
spans_to_delete_map: set[str] = set()
|
|
74
|
-
events_iterations_map: dict[str, dict[str, str]] = {}
|
|
75
|
-
|
|
76
|
-
def clean_span_sources(span_id: str) -> None:
|
|
77
|
-
span = spans_map.get(span_id)
|
|
78
|
-
parent_id = span.get("parent_id") if span else None
|
|
79
|
-
if not parent_id:
|
|
80
|
-
return
|
|
81
|
-
|
|
82
|
-
span_count = parent_ids_map.get(parent_id)
|
|
83
|
-
if not span_count:
|
|
84
|
-
return
|
|
85
|
-
|
|
86
|
-
if span_count > 1:
|
|
87
|
-
parent_ids_map[parent_id] = span_count - 1
|
|
88
|
-
elif span_count == 1:
|
|
89
|
-
parent_ids_map.pop(parent_id)
|
|
90
|
-
if parent_id in spans_to_delete_map:
|
|
91
|
-
spans_map.pop(parent_id, None)
|
|
92
|
-
spans_to_delete_map.discard(parent_id)
|
|
93
|
-
|
|
94
|
-
history: list[dict[str, Any]] = []
|
|
95
|
-
generated_message: Optional[dict[str, Any]] = None
|
|
96
|
-
group_iterations = []
|
|
97
|
-
|
|
98
|
-
start_time_perf = time.time_ns()
|
|
99
|
-
|
|
100
|
-
def datetime_to_ns(dt: datetime) -> int:
|
|
101
|
-
epoch = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
|
102
|
-
return int((dt - epoch).total_seconds() * 1e9)
|
|
103
|
-
|
|
104
|
-
def on_finish(_data: Any, _meta: EventMeta) -> None:
|
|
105
|
-
nonlocal prompt
|
|
106
|
-
try:
|
|
107
|
-
if not prompt and isinstance(instance, BaseAgent):
|
|
108
|
-
prompt = next(
|
|
109
|
-
(m.text for m in reversed(instance.memory.messages) if m.role == Role.USER),
|
|
110
|
-
None,
|
|
111
|
-
)
|
|
112
|
-
if not prompt:
|
|
113
|
-
raise FrameworkError("The prompt must be defined", context=context.__dict__)
|
|
114
|
-
|
|
115
|
-
## This would call your tree-building logic
|
|
116
|
-
build_trace_tree(
|
|
117
|
-
tracer=tracer,
|
|
118
|
-
main_span_kind=main_span_kind,
|
|
119
|
-
data={
|
|
120
|
-
"prompt": prompt,
|
|
121
|
-
"history": history,
|
|
122
|
-
"generatedMessage": generated_message,
|
|
123
|
-
"spans": list(spans_map.values()),
|
|
124
|
-
"traceId": trace_id,
|
|
125
|
-
"version": Version,
|
|
126
|
-
"startTime": start_time_perf,
|
|
127
|
-
"endTime": time.time_ns(),
|
|
128
|
-
"source": active_traces_map.get(trace_id),
|
|
129
|
-
"run_error_span_key": f"run.{base_path}.error",
|
|
130
|
-
},
|
|
131
|
-
)
|
|
132
|
-
except Exception as e:
|
|
133
|
-
logger.error("Instrumentation send data error", exc_info=e)
|
|
134
|
-
finally:
|
|
135
|
-
del active_traces_map[trace_id]
|
|
136
|
-
|
|
137
|
-
emitter.match(f"run.{base_path}.finish", on_finish)
|
|
138
|
-
|
|
139
|
-
def on_any_event(data: Any, meta: EventMeta) -> None:
|
|
140
|
-
nonlocal context
|
|
141
|
-
if meta.path.startswith("run.") and meta.name != "run.error":
|
|
142
|
-
return
|
|
143
|
-
|
|
144
|
-
if meta.name == "new_token":
|
|
145
|
-
return
|
|
146
|
-
|
|
147
|
-
if not getattr(meta.trace, "run_id", None):
|
|
148
|
-
raise FrameworkError(
|
|
149
|
-
f"Fatal error. Missing run_id for event: {meta.path}", context=context.__dict__
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
try:
|
|
153
|
-
iteration_event_name = meta.group_id.strip("`") if meta.group_id else meta.group_id
|
|
154
|
-
if (
|
|
155
|
-
iteration_event_name
|
|
156
|
-
and not getattr(meta.trace, "parent_run_id", None)
|
|
157
|
-
and iteration_event_name not in group_iterations
|
|
158
|
-
):
|
|
159
|
-
spans_map[iteration_event_name] = create_span(
|
|
160
|
-
id=iteration_event_name,
|
|
161
|
-
name=iteration_event_name,
|
|
162
|
-
target="groupId",
|
|
163
|
-
data={
|
|
164
|
-
SpanAttributes.OPENINFERENCE_SPAN_KIND: OpenInferenceSpanKindValues.CHAIN.value # noqa: E501
|
|
165
|
-
},
|
|
166
|
-
started_at=datetime_to_ns(meta.created_at),
|
|
167
|
-
)
|
|
168
|
-
group_iterations.append(iteration_event_name)
|
|
169
|
-
|
|
170
|
-
ids = id_name_manager.get_ids(
|
|
171
|
-
path=meta.path,
|
|
172
|
-
id=meta.id,
|
|
173
|
-
run_id=meta.trace.run_id, # type: ignore
|
|
174
|
-
parent_run_id=getattr(meta.trace, "parent_run_id", None),
|
|
175
|
-
group_id=iteration_event_name,
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
span_id = ids["spanId"]
|
|
179
|
-
parent_span_id = ids["parentSpanId"]
|
|
180
|
-
|
|
181
|
-
serialized_data = get_serialized_object_safe(data, meta)
|
|
182
|
-
# Skip partialUpdate events with no data
|
|
183
|
-
if meta.name == "partial_update" and not serialized_data:
|
|
184
|
-
return
|
|
185
|
-
|
|
186
|
-
span = create_span(
|
|
187
|
-
id=span_id,
|
|
188
|
-
name=meta.name,
|
|
189
|
-
target=meta.path,
|
|
190
|
-
parent={"id": parent_span_id} if parent_span_id else None,
|
|
191
|
-
ctx=getattr(meta, "context", None),
|
|
192
|
-
data=serialized_data,
|
|
193
|
-
started_at=datetime_to_ns(meta.created_at),
|
|
194
|
-
)
|
|
195
|
-
spans_map[span["context"]["span_id"]] = span
|
|
196
|
-
|
|
197
|
-
last_iteration = group_iterations[-1] if group_iterations else None
|
|
198
|
-
|
|
199
|
-
# Clean up `partial_update` event if no nested spans
|
|
200
|
-
last_iteration_event_span_id: Optional[str] = (
|
|
201
|
-
events_iterations_map.get(last_iteration, {}).get(meta.name)
|
|
202
|
-
if last_iteration is not None
|
|
203
|
-
else None
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
last_iteration_event_span_id
|
|
208
|
-
and meta.name == "partial_update"
|
|
209
|
-
and last_iteration_event_span_id in spans_map
|
|
210
|
-
):
|
|
211
|
-
if span_id and span_id in parent_ids_map:
|
|
212
|
-
spans_to_delete_map.add(last_iteration_event_span_id)
|
|
213
|
-
else:
|
|
214
|
-
clean_span_sources(last_iteration_event_span_id)
|
|
215
|
-
spans_map.pop(last_iteration_event_span_id, None)
|
|
216
|
-
|
|
217
|
-
# Create new span
|
|
218
|
-
spans_map[span["context"]["span_id"]] = span
|
|
219
|
-
|
|
220
|
-
# Update parent count
|
|
221
|
-
if span.get("parent_id"):
|
|
222
|
-
parent_id = span["parent_id"]
|
|
223
|
-
if parent_id is not None:
|
|
224
|
-
parent_ids_map[parent_id] = parent_ids_map.get(parent_id, 0) + 1
|
|
225
|
-
|
|
226
|
-
# Save the last event for each iteration
|
|
227
|
-
if group_iterations:
|
|
228
|
-
if last_iteration in events_iterations_map:
|
|
229
|
-
events_iterations_map[last_iteration][meta.name] = span["context"][
|
|
230
|
-
"span_id"
|
|
231
|
-
]
|
|
232
|
-
elif last_iteration is not None:
|
|
233
|
-
events_iterations_map[last_iteration] = {
|
|
234
|
-
meta.name: span["context"]["span_id"]
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
except Exception as e:
|
|
238
|
-
logger.error("Instrumentation build data error", exc_info=e)
|
|
239
|
-
|
|
240
|
-
emitter.match("*.*", on_any_event)
|
|
241
|
-
|
|
242
|
-
def is_success_event(event: Any) -> bool:
|
|
243
|
-
return getattr(event, "name", None) == "success" and isinstance(
|
|
244
|
-
getattr(event, "creator", None), BaseAgent
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
def on_success(data: Any, meta: EventMeta) -> None:
|
|
248
|
-
nonlocal generated_message, history
|
|
249
|
-
try:
|
|
250
|
-
if isinstance(meta.creator, ToolCallingAgent):
|
|
251
|
-
tool_calling_typed_data = cast(ToolCallingAgentSuccessEvent, data)
|
|
252
|
-
history = [
|
|
253
|
-
{
|
|
254
|
-
"text": m.text,
|
|
255
|
-
"role": m.role.value if hasattr(m.role, "value") else m.role,
|
|
256
|
-
}
|
|
257
|
-
for m in tool_calling_typed_data.state.memory.messages
|
|
258
|
-
]
|
|
259
|
-
if (
|
|
260
|
-
hasattr(tool_calling_typed_data.state, "result")
|
|
261
|
-
and tool_calling_typed_data.state.result is not None
|
|
262
|
-
):
|
|
263
|
-
result_role = tool_calling_typed_data.state.result.role
|
|
264
|
-
generated_message = {
|
|
265
|
-
"role": result_role.value
|
|
266
|
-
if hasattr(result_role, "value")
|
|
267
|
-
else result_role,
|
|
268
|
-
"text": tool_calling_typed_data.state.result.text,
|
|
269
|
-
}
|
|
270
|
-
if isinstance(meta.creator, ReActAgent):
|
|
271
|
-
react_agent_typed_data = cast(ReActAgentSuccessEvent, data)
|
|
272
|
-
tooling_result_role = react_agent_typed_data.data.role
|
|
273
|
-
generated_message = {
|
|
274
|
-
"role": tooling_result_role.value
|
|
275
|
-
if hasattr(tooling_result_role, "value")
|
|
276
|
-
else tooling_result_role,
|
|
277
|
-
"text": react_agent_typed_data.data.text,
|
|
278
|
-
}
|
|
279
|
-
history = [
|
|
280
|
-
{
|
|
281
|
-
"text": m.text,
|
|
282
|
-
"role": m.role.value if hasattr(m.role, "value") else m.role,
|
|
283
|
-
}
|
|
284
|
-
for m in react_agent_typed_data.memory.messages
|
|
285
|
-
]
|
|
286
|
-
except Exception as e:
|
|
287
|
-
logger.error("Instrumentation error: failed to extract success message", exc_info=e)
|
|
288
|
-
|
|
289
|
-
emitter.match(is_success_event, on_success)
|
|
290
|
-
|
|
291
|
-
return middleware
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 IBM Corp.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
from typing import Any, List, Optional, TypedDict
|
|
16
|
-
|
|
17
|
-
from opentelemetry import trace
|
|
18
|
-
|
|
19
|
-
from openinference.instrumentation import OITracer
|
|
20
|
-
from openinference.semconv.trace import OpenInferenceSpanKindValues, SpanAttributes
|
|
21
|
-
|
|
22
|
-
from .create_span import FrameworkSpan
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class BuildTraceTreeData(TypedDict):
|
|
26
|
-
prompt: Optional[str]
|
|
27
|
-
history: list[dict[str, Any]]
|
|
28
|
-
generatedMessage: dict[str, Any] | None
|
|
29
|
-
spans: List[FrameworkSpan]
|
|
30
|
-
traceId: str
|
|
31
|
-
version: str
|
|
32
|
-
startTime: int
|
|
33
|
-
endTime: int
|
|
34
|
-
source: Optional[str]
|
|
35
|
-
run_error_span_key: str
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def build_spans_for_parent(
|
|
39
|
-
tracer: OITracer, spans: List[FrameworkSpan], trace_id: str, parent_id: str | None
|
|
40
|
-
) -> None:
|
|
41
|
-
children = [s for s in spans if s.get("parent_id") == parent_id]
|
|
42
|
-
|
|
43
|
-
for span in children:
|
|
44
|
-
attributes = {
|
|
45
|
-
"target": span["attributes"]["target"],
|
|
46
|
-
"name": span.get("name"),
|
|
47
|
-
"traceId": trace_id,
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if span["attributes"]["metadata"]:
|
|
51
|
-
attributes["metadata"] = str(span["attributes"]["metadata"])
|
|
52
|
-
|
|
53
|
-
if span["attributes"]["data"]:
|
|
54
|
-
attributes.update(span["attributes"]["data"])
|
|
55
|
-
|
|
56
|
-
with tracer.start_as_current_span(
|
|
57
|
-
name=span["context"]["span_id"],
|
|
58
|
-
attributes={k: v for k, v in attributes.items() if v is not None},
|
|
59
|
-
start_time=span.get("start_time"),
|
|
60
|
-
) as current_child:
|
|
61
|
-
status = span.get("status")
|
|
62
|
-
if status is not None:
|
|
63
|
-
current_child.set_status(status["code"])
|
|
64
|
-
build_spans_for_parent(
|
|
65
|
-
tracer=tracer,
|
|
66
|
-
spans=spans,
|
|
67
|
-
trace_id=trace_id,
|
|
68
|
-
parent_id=span["context"]["span_id"],
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def build_agent_main_span_data(data: BuildTraceTreeData) -> dict[str, Any]:
|
|
73
|
-
payload = {}
|
|
74
|
-
if data.get("prompt"):
|
|
75
|
-
payload[SpanAttributes.INPUT_VALUE] = data.get("prompt")
|
|
76
|
-
if data.get("generatedMessage"):
|
|
77
|
-
payload[SpanAttributes.OUTPUT_VALUE] = str(data.get("generatedMessage"))
|
|
78
|
-
if data.get("history"):
|
|
79
|
-
payload["history"] = str(data.get("history"))
|
|
80
|
-
return payload
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def build_tool_main_span_data(data: BuildTraceTreeData) -> dict[str, Any]:
|
|
84
|
-
start_span = next((s for s in data["spans"] if s["name"] == "start"), None)
|
|
85
|
-
success_span = next((s for s in data["spans"] if s["name"] == "success"), None)
|
|
86
|
-
|
|
87
|
-
payload = {}
|
|
88
|
-
if start_span and start_span["attributes"]["data"] is not None:
|
|
89
|
-
tool_params = start_span["attributes"]["data"].get(SpanAttributes.TOOL_PARAMETERS)
|
|
90
|
-
payload[SpanAttributes.INPUT_VALUE] = tool_params
|
|
91
|
-
payload[SpanAttributes.TOOL_PARAMETERS] = tool_params
|
|
92
|
-
if success_span and success_span["attributes"]["data"] is not None:
|
|
93
|
-
payload[SpanAttributes.OUTPUT_VALUE] = success_span["attributes"]["data"].get(
|
|
94
|
-
SpanAttributes.OUTPUT_VALUE
|
|
95
|
-
)
|
|
96
|
-
return payload
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def build_llm_main_span_data(data: BuildTraceTreeData) -> dict[str, Any]:
|
|
100
|
-
start_span = next((s for s in data["spans"] if s["name"] == "start"), None)
|
|
101
|
-
success_span = next((s for s in data["spans"] if s["name"] == "success"), None)
|
|
102
|
-
provider = None
|
|
103
|
-
model_name = None
|
|
104
|
-
|
|
105
|
-
if start_span and start_span["attributes"]["data"] is not None:
|
|
106
|
-
provider = start_span["attributes"]["data"].get(SpanAttributes.LLM_PROVIDER)
|
|
107
|
-
model_name = start_span["attributes"]["data"].get(SpanAttributes.LLM_MODEL_NAME)
|
|
108
|
-
|
|
109
|
-
if success_span and success_span["attributes"]["data"] is not None:
|
|
110
|
-
provider = provider or success_span["attributes"]["data"].get(SpanAttributes.LLM_PROVIDER)
|
|
111
|
-
model_name = model_name or success_span["attributes"]["data"].get(
|
|
112
|
-
SpanAttributes.LLM_MODEL_NAME
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
payload = {}
|
|
116
|
-
if start_span and start_span["attributes"]["data"] is not None:
|
|
117
|
-
payload[SpanAttributes.INPUT_VALUE] = start_span["attributes"]["data"].get(
|
|
118
|
-
SpanAttributes.INPUT_VALUE
|
|
119
|
-
)
|
|
120
|
-
payload[SpanAttributes.INPUT_MIME_TYPE] = start_span["attributes"]["data"].get(
|
|
121
|
-
SpanAttributes.INPUT_MIME_TYPE
|
|
122
|
-
)
|
|
123
|
-
if success_span and success_span["attributes"]["data"] is not None:
|
|
124
|
-
payload[SpanAttributes.OUTPUT_VALUE] = success_span["attributes"]["data"].get(
|
|
125
|
-
SpanAttributes.OUTPUT_VALUE
|
|
126
|
-
)
|
|
127
|
-
payload[SpanAttributes.OUTPUT_MIME_TYPE] = success_span["attributes"]["data"].get(
|
|
128
|
-
SpanAttributes.OUTPUT_MIME_TYPE
|
|
129
|
-
)
|
|
130
|
-
if provider:
|
|
131
|
-
payload[SpanAttributes.LLM_PROVIDER] = provider
|
|
132
|
-
if model_name:
|
|
133
|
-
payload[SpanAttributes.LLM_MODEL_NAME] = model_name
|
|
134
|
-
|
|
135
|
-
return payload
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def build_trace_tree(tracer: OITracer, main_span_kind: str, data: BuildTraceTreeData) -> None:
|
|
139
|
-
if main_span_kind == OpenInferenceSpanKindValues.AGENT.value:
|
|
140
|
-
computed_data = build_agent_main_span_data(data)
|
|
141
|
-
elif main_span_kind == OpenInferenceSpanKindValues.TOOL.value:
|
|
142
|
-
computed_data = build_tool_main_span_data(data)
|
|
143
|
-
elif main_span_kind == OpenInferenceSpanKindValues.LLM.value:
|
|
144
|
-
computed_data = build_llm_main_span_data(data)
|
|
145
|
-
else:
|
|
146
|
-
computed_data = {}
|
|
147
|
-
|
|
148
|
-
attributes = {
|
|
149
|
-
"source": data["source"],
|
|
150
|
-
"traceId": data["traceId"],
|
|
151
|
-
SpanAttributes.OPENINFERENCE_SPAN_KIND: main_span_kind,
|
|
152
|
-
"beeai.version": data["version"],
|
|
153
|
-
**computed_data,
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
with tracer.start_as_current_span(
|
|
157
|
-
name="beeai-framework-main",
|
|
158
|
-
start_time=data["startTime"],
|
|
159
|
-
attributes=attributes,
|
|
160
|
-
) as current_span:
|
|
161
|
-
run_error_span = next(
|
|
162
|
-
(s for s in data["spans"] if s["attributes"]["target"] == data["run_error_span_key"]),
|
|
163
|
-
None,
|
|
164
|
-
)
|
|
165
|
-
if run_error_span is not None:
|
|
166
|
-
current_span.set_status(trace.StatusCode.ERROR)
|
|
167
|
-
else:
|
|
168
|
-
current_span.set_status(trace.StatusCode.OK)
|
|
169
|
-
|
|
170
|
-
build_spans_for_parent(tracer, data["spans"], data["traceId"], parent_id=None)
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 IBM Corp.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
import time
|
|
16
|
-
from typing import Any, Dict, Optional, TypedDict
|
|
17
|
-
|
|
18
|
-
from opentelemetry.trace import StatusCode
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class SpanContext(TypedDict):
|
|
22
|
-
span_id: str
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class SpanStatus(TypedDict):
|
|
26
|
-
code: StatusCode
|
|
27
|
-
message: str
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class SpanAttributes(TypedDict, total=False):
|
|
31
|
-
target: str
|
|
32
|
-
metadata: Optional[Dict[str, Any]]
|
|
33
|
-
data: Optional[Dict[str, Any]]
|
|
34
|
-
exception_message: Optional[str]
|
|
35
|
-
exception_type: Optional[str]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class FrameworkSpan(TypedDict):
|
|
39
|
-
name: str
|
|
40
|
-
attributes: SpanAttributes
|
|
41
|
-
context: SpanContext
|
|
42
|
-
parent_id: Optional[str]
|
|
43
|
-
status: SpanStatus
|
|
44
|
-
start_time: int
|
|
45
|
-
end_time: int
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def create_span(
|
|
49
|
-
*,
|
|
50
|
-
id: str,
|
|
51
|
-
name: str,
|
|
52
|
-
target: str,
|
|
53
|
-
started_at: int,
|
|
54
|
-
ctx: Optional[Dict[str, Any]] = None,
|
|
55
|
-
data: Optional[Dict[str, Any]] = None,
|
|
56
|
-
error: Optional[str] = None,
|
|
57
|
-
parent: Optional[Dict[str, str]] = None,
|
|
58
|
-
) -> FrameworkSpan:
|
|
59
|
-
attributes: SpanAttributes = {
|
|
60
|
-
"target": target,
|
|
61
|
-
"metadata": ctx if ctx else None,
|
|
62
|
-
"data": data if data else None,
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if error:
|
|
66
|
-
attributes["exception_message"] = error
|
|
67
|
-
attributes["exception_type"] = "FrameworkError"
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
"name": name,
|
|
71
|
-
"attributes": attributes,
|
|
72
|
-
"context": {"span_id": id},
|
|
73
|
-
"parent_id": parent["id"] if parent else None,
|
|
74
|
-
"status": {
|
|
75
|
-
"code": StatusCode.ERROR if error else StatusCode.OK,
|
|
76
|
-
"message": error or "",
|
|
77
|
-
},
|
|
78
|
-
"start_time": started_at,
|
|
79
|
-
"end_time": time.time_ns(),
|
|
80
|
-
}
|