openinference-instrumentation-beeai 0.1.6__py3-none-any.whl → 0.1.8__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.
Files changed (27) hide show
  1. openinference/instrumentation/beeai/__init__.py +78 -104
  2. openinference/instrumentation/beeai/_span.py +81 -0
  3. openinference/instrumentation/beeai/_utils.py +77 -0
  4. openinference/instrumentation/beeai/processors/__init__.py +0 -0
  5. openinference/instrumentation/beeai/processors/agents/__init__.py +0 -0
  6. openinference/instrumentation/beeai/processors/agents/base.py +34 -0
  7. openinference/instrumentation/beeai/processors/agents/react.py +77 -0
  8. openinference/instrumentation/beeai/processors/agents/requirement_agent.py +71 -0
  9. openinference/instrumentation/beeai/processors/agents/tool_calling.py +34 -0
  10. openinference/instrumentation/beeai/processors/base.py +60 -0
  11. openinference/instrumentation/beeai/processors/chat.py +245 -0
  12. openinference/instrumentation/beeai/processors/embedding.py +71 -0
  13. openinference/instrumentation/beeai/processors/locator.py +106 -0
  14. openinference/instrumentation/beeai/processors/requirement.py +67 -0
  15. openinference/instrumentation/beeai/processors/tool.py +68 -0
  16. openinference/instrumentation/beeai/processors/workflow.py +108 -0
  17. openinference/instrumentation/beeai/version.py +1 -1
  18. {openinference_instrumentation_beeai-0.1.6.dist-info → openinference_instrumentation_beeai-0.1.8.dist-info}/METADATA +12 -9
  19. openinference_instrumentation_beeai-0.1.8.dist-info/RECORD +21 -0
  20. openinference/instrumentation/beeai/middleware.py +0 -291
  21. openinference/instrumentation/beeai/utils/build_trace_tree.py +0 -170
  22. openinference/instrumentation/beeai/utils/create_span.py +0 -80
  23. openinference/instrumentation/beeai/utils/get_serialized_object_safe.py +0 -302
  24. openinference/instrumentation/beeai/utils/id_name_manager.py +0 -58
  25. openinference_instrumentation_beeai-0.1.6.dist-info/RECORD +0 -11
  26. {openinference_instrumentation_beeai-0.1.6.dist-info → openinference_instrumentation_beeai-0.1.8.dist-info}/WHEEL +0 -0
  27. {openinference_instrumentation_beeai-0.1.6.dist-info → openinference_instrumentation_beeai-0.1.8.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.6
3
+ Version: 0.1.8
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-email: Milan Gallas <gallas.milan@gmail.com>
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: openinference-instrumentation>=0.1.27
18
- Requires-Dist: openinference-semantic-conventions>=0.1.17
19
- Requires-Dist: opentelemetry-api>=1.31.1
20
- Requires-Dist: opentelemetry-instrumentation>=0.52b1
21
- Requires-Dist: opentelemetry-semantic-conventions>=0.52b1
18
+ Requires-Dist: beeai-framework<0.2.0,>=0.1.36
19
+ Requires-Dist: openinference-instrumentation>=0.1.37
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>=0.1.10; extra == 'instruments'
26
+ Requires-Dist: beeai-framework>=0.1.36; extra == 'instruments'
25
27
  Provides-Extra: test
26
- Requires-Dist: beeai-framework>=0.1.19; extra == 'test'
28
+ Requires-Dist: beeai-framework>=0.1.36; 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=m4ZXoGLm-iJGAJBjrfSh06OU2EaDMNgStVTLbljmPSA,2328
4
+ openinference/instrumentation/beeai/version.py,sha256=C69ADlbQREQlR15trneyA2sk8x0-oH4rDAX5fsv19_U,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=a4Ps6opRb2POrQ8Nla1JHUhkdVFZF5rmht8Xca3F_xA,9285
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=03pNF04ELsxRl8EK3JhHktoQl_aXAY1rrXHV4QuXcJk,2533
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.8.dist-info/METADATA,sha256=Jp_y16JgscRpObUhhtwhgRW-nG3MwTDKQIgQA_oYFxM,5491
19
+ openinference_instrumentation_beeai-0.1.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
+ openinference_instrumentation_beeai-0.1.8.dist-info/entry_points.txt,sha256=ee7EUhbWv-XK1dxhPXuFVy9qstzj-lc-265Phe2Ml9s,183
21
+ openinference_instrumentation_beeai-0.1.8.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
- }