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.
- openinference/instrumentation/beeai/__init__.py +78 -104
- openinference/instrumentation/beeai/_span.py +81 -0
- openinference/instrumentation/beeai/_utils.py +77 -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 +245 -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 +68 -0
- openinference/instrumentation/beeai/processors/workflow.py +108 -0
- openinference/instrumentation/beeai/version.py +1 -1
- {openinference_instrumentation_beeai-0.1.6.dist-info → openinference_instrumentation_beeai-0.1.8.dist-info}/METADATA +12 -9
- openinference_instrumentation_beeai-0.1.8.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.6.dist-info/RECORD +0 -11
- {openinference_instrumentation_beeai-0.1.6.dist-info → openinference_instrumentation_beeai-0.1.8.dist-info}/WHEEL +0 -0
- {openinference_instrumentation_beeai-0.1.6.dist-info → openinference_instrumentation_beeai-0.1.8.dist-info}/entry_points.txt +0 -0
|
@@ -1,23 +1,10 @@
|
|
|
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
1
|
import logging
|
|
16
|
-
from importlib import import_module
|
|
17
2
|
from importlib.metadata import PackageNotFoundError, version
|
|
18
|
-
from typing import Any, Callable, Collection
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable, Collection
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from beeai_framework.emitter import EventMeta
|
|
19
7
|
|
|
20
|
-
import wrapt
|
|
21
8
|
from opentelemetry import trace as trace_api
|
|
22
9
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor # type: ignore
|
|
23
10
|
|
|
@@ -25,13 +12,14 @@ from openinference.instrumentation import (
|
|
|
25
12
|
OITracer,
|
|
26
13
|
TraceConfig,
|
|
27
14
|
)
|
|
28
|
-
from openinference.
|
|
29
|
-
|
|
30
|
-
from .
|
|
15
|
+
from openinference.instrumentation.beeai._span import SpanWrapper
|
|
16
|
+
from openinference.instrumentation.beeai._utils import _datetime_to_span_time, exception_handler
|
|
17
|
+
from openinference.instrumentation.beeai.processors.base import Processor
|
|
18
|
+
from openinference.instrumentation.beeai.processors.locator import ProcessorLocator
|
|
31
19
|
|
|
32
20
|
logger = logging.getLogger(__name__)
|
|
33
21
|
|
|
34
|
-
_instruments = ("beeai-framework >= 0.1.
|
|
22
|
+
_instruments = ("beeai-framework >= 0.1.32",)
|
|
35
23
|
try:
|
|
36
24
|
__version__ = version("beeai-framework")
|
|
37
25
|
except PackageNotFoundError:
|
|
@@ -39,14 +27,12 @@ except PackageNotFoundError:
|
|
|
39
27
|
|
|
40
28
|
|
|
41
29
|
class BeeAIInstrumentor(BaseInstrumentor): # type: ignore
|
|
42
|
-
__slots__ = (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"_tracer",
|
|
49
|
-
)
|
|
30
|
+
__slots__ = ("_tracer", "_cleanup", "_processes")
|
|
31
|
+
|
|
32
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
33
|
+
super().__init__(*args, **kwargs)
|
|
34
|
+
self._cleanup: Callable[[], None] = lambda: None
|
|
35
|
+
self._processes: dict[str, Processor] = {}
|
|
50
36
|
|
|
51
37
|
def instrumentation_dependencies(self) -> Collection[str]:
|
|
52
38
|
return _instruments
|
|
@@ -60,89 +46,77 @@ class BeeAIInstrumentor(BaseInstrumentor): # type: ignore
|
|
|
60
46
|
else:
|
|
61
47
|
assert isinstance(config, TraceConfig)
|
|
62
48
|
|
|
63
|
-
from beeai_framework.agents.base import BaseAgent
|
|
64
|
-
from beeai_framework.agents.react.agent import ReActAgent
|
|
65
|
-
from beeai_framework.agents.tool_calling.agent import ToolCallingAgent
|
|
66
|
-
from beeai_framework.backend.chat import ChatModel
|
|
67
|
-
from beeai_framework.tools import Tool
|
|
68
|
-
|
|
69
49
|
self._tracer = OITracer(
|
|
70
50
|
trace_api.get_tracer(__name__, __version__, tracer_provider),
|
|
71
51
|
config=config,
|
|
72
52
|
)
|
|
73
53
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@wrapt.decorator # type: ignore
|
|
77
|
-
def run_wrapper(
|
|
78
|
-
wrapped: F, instance: Any, args: tuple[Any, ...], kwargs: dict[str, Any]
|
|
79
|
-
) -> Any:
|
|
80
|
-
result = wrapped(*args, **kwargs)
|
|
81
|
-
|
|
82
|
-
span_kind = OpenInferenceSpanKindValues.UNKNOWN
|
|
83
|
-
if isinstance(instance, ChatModel):
|
|
84
|
-
span_kind = OpenInferenceSpanKindValues.LLM
|
|
85
|
-
if isinstance(instance, BaseAgent):
|
|
86
|
-
span_kind = OpenInferenceSpanKindValues.AGENT
|
|
87
|
-
if isinstance(instance, Tool):
|
|
88
|
-
span_kind = OpenInferenceSpanKindValues.TOOL
|
|
89
|
-
|
|
90
|
-
if result.middleware:
|
|
91
|
-
result.middleware(create_telemetry_middleware(self._tracer, span_kind.value))
|
|
54
|
+
from beeai_framework.emitter import Emitter, EmitterOptions
|
|
92
55
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
import_module("beeai_framework.agents.react.agent"), "run", None
|
|
98
|
-
)
|
|
99
|
-
setattr(ReActAgent, "run", run_wrapper(ReActAgent.run))
|
|
100
|
-
self._original_tool_calling_agent_run = getattr(
|
|
101
|
-
import_module("beeai_framework.agents.tool_calling.agent"), "run", None
|
|
102
|
-
)
|
|
103
|
-
setattr(ToolCallingAgent, "run", run_wrapper(ToolCallingAgent.run))
|
|
104
|
-
## LLM support
|
|
105
|
-
self._original_chat_model_create = getattr(
|
|
106
|
-
import_module("beeai_framework.backend.chat"), "create", None
|
|
107
|
-
)
|
|
108
|
-
setattr(ChatModel, "create", run_wrapper(ChatModel.create))
|
|
109
|
-
self._original_chat_model_create_structure = getattr(
|
|
110
|
-
import_module("beeai_framework.backend.chat"), "create_structure", None
|
|
56
|
+
self._cleanup = Emitter.root().match(
|
|
57
|
+
"*.*",
|
|
58
|
+
self._handler,
|
|
59
|
+
EmitterOptions(match_nested=True, is_blocking=True),
|
|
111
60
|
)
|
|
112
|
-
setattr(ChatModel, "create_structure", run_wrapper(ChatModel.create_structure))
|
|
113
|
-
|
|
114
|
-
## Tool support
|
|
115
|
-
self._original_tool_run = getattr(import_module("beeai_framework.tools"), "run", None)
|
|
116
|
-
setattr(Tool, "run", run_wrapper(Tool.run))
|
|
117
|
-
|
|
118
|
-
except ImportError as e:
|
|
119
|
-
logger.error("ImportError during instrumentation", exc_info=e)
|
|
120
61
|
except Exception as e:
|
|
121
62
|
logger.error("Instrumentation error", exc_info=e)
|
|
122
63
|
|
|
123
64
|
def _uninstrument(self, **kwargs: Any) -> None:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
65
|
+
self._cleanup()
|
|
66
|
+
self._processes.clear()
|
|
67
|
+
|
|
68
|
+
def _build_tree(self, node: SpanWrapper) -> None:
|
|
69
|
+
with self._tracer.start_as_current_span(
|
|
70
|
+
name=node.name,
|
|
71
|
+
openinference_span_kind=node.kind,
|
|
72
|
+
attributes=node.attributes,
|
|
73
|
+
start_time=_datetime_to_span_time(node.started_at) if node.started_at else None,
|
|
74
|
+
end_on_exit=False, # we do it manually
|
|
75
|
+
) as current_span:
|
|
76
|
+
for event in node.events:
|
|
77
|
+
current_span.add_event(
|
|
78
|
+
name=event.name, attributes=event.attributes, timestamp=event.timestamp
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
for children in node.children:
|
|
82
|
+
self._build_tree(children)
|
|
83
|
+
|
|
84
|
+
current_span.set_status(node.status)
|
|
85
|
+
if node.error is not None:
|
|
86
|
+
current_span.record_exception(node.error)
|
|
87
|
+
|
|
88
|
+
current_span.end(_datetime_to_span_time(node.ended_at) if node.ended_at else None)
|
|
89
|
+
|
|
90
|
+
@exception_handler
|
|
91
|
+
async def _handler(self, data: Any, event: "EventMeta") -> None:
|
|
92
|
+
assert event.trace is not None, "Event must have a trace"
|
|
93
|
+
|
|
94
|
+
if event.trace.run_id not in self._processes:
|
|
95
|
+
parent = (
|
|
96
|
+
self._processes.get(event.trace.parent_run_id)
|
|
97
|
+
if event.trace.parent_run_id
|
|
98
|
+
else None
|
|
99
|
+
)
|
|
100
|
+
if event.trace.parent_run_id and not parent:
|
|
101
|
+
raise ValueError(f"Parent run with ID {event.trace.parent_run_id} was not found!")
|
|
102
|
+
|
|
103
|
+
node = self._processes[event.trace.run_id] = ProcessorLocator.locate(data, event)
|
|
104
|
+
if parent is not None:
|
|
105
|
+
parent.span.children.append(node.span)
|
|
106
|
+
else:
|
|
107
|
+
node = self._processes[event.trace.run_id]
|
|
108
|
+
|
|
109
|
+
from beeai_framework.context import RunContextFinishEvent
|
|
110
|
+
|
|
111
|
+
if isinstance(data, RunContextFinishEvent):
|
|
112
|
+
await node.end(data, event)
|
|
113
|
+
self._build_tree(node.span)
|
|
114
|
+
self._processes.pop(event.trace.run_id)
|
|
115
|
+
else:
|
|
116
|
+
if event.context.get("internal"):
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
await node.update(
|
|
120
|
+
data,
|
|
121
|
+
event,
|
|
122
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import TYPE_CHECKING, Any
|
|
3
|
+
|
|
4
|
+
from opentelemetry.sdk.trace import Event
|
|
5
|
+
from opentelemetry.trace import StatusCode
|
|
6
|
+
|
|
7
|
+
from openinference.instrumentation.beeai._utils import _datetime_to_span_time, _unpack_object
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from beeai_framework.emitter import EventMeta
|
|
11
|
+
|
|
12
|
+
from openinference.semconv.trace import (
|
|
13
|
+
OpenInferenceMimeTypeValues,
|
|
14
|
+
OpenInferenceSpanKindValues,
|
|
15
|
+
SpanAttributes,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SpanWrapper:
|
|
20
|
+
def __init__(self, *, name: str, kind: OpenInferenceSpanKindValues):
|
|
21
|
+
self.name = name
|
|
22
|
+
self.attributes: dict[str, Any] = {SpanAttributes.OPENINFERENCE_SPAN_KIND: kind}
|
|
23
|
+
self.events: list[Event] = []
|
|
24
|
+
self.status: StatusCode = StatusCode.OK
|
|
25
|
+
self.error: Exception | None = None
|
|
26
|
+
self.started_at: datetime | None = None
|
|
27
|
+
self.ended_at: datetime | None = None
|
|
28
|
+
self.children: list["SpanWrapper"] = []
|
|
29
|
+
self.kind = kind
|
|
30
|
+
|
|
31
|
+
def child(
|
|
32
|
+
self, name: str | None = None, event: tuple[Any, "EventMeta"] | None = None
|
|
33
|
+
) -> "SpanWrapper":
|
|
34
|
+
child = SpanWrapper(name=name or f"{self.name}_child", kind=self.kind)
|
|
35
|
+
if event is not None:
|
|
36
|
+
from beeai_framework.utils.dicts import include_keys
|
|
37
|
+
from beeai_framework.utils.strings import to_json
|
|
38
|
+
|
|
39
|
+
value, meta = event
|
|
40
|
+
|
|
41
|
+
child.started_at = meta.created_at
|
|
42
|
+
child.ended_at = meta.created_at
|
|
43
|
+
child.attributes.update(
|
|
44
|
+
{
|
|
45
|
+
SpanAttributes.INPUT_VALUE: to_json(value) if value is not None else value,
|
|
46
|
+
SpanAttributes.INPUT_MIME_TYPE: OpenInferenceMimeTypeValues.JSON.value,
|
|
47
|
+
**_unpack_object(
|
|
48
|
+
include_keys(meta.model_dump(), {"id", "context", "path", "trace"}),
|
|
49
|
+
prefix=SpanAttributes.METADATA,
|
|
50
|
+
),
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self.children.append(child)
|
|
55
|
+
return child
|
|
56
|
+
|
|
57
|
+
def add_event(
|
|
58
|
+
self,
|
|
59
|
+
name: str,
|
|
60
|
+
attributes: dict[str, Any] | None = None,
|
|
61
|
+
timestamp: datetime | None = None,
|
|
62
|
+
) -> None:
|
|
63
|
+
self.events.append(
|
|
64
|
+
Event(
|
|
65
|
+
name=name,
|
|
66
|
+
attributes=attributes or {},
|
|
67
|
+
timestamp=_datetime_to_span_time(timestamp) if timestamp else None,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def set_attribute(self, name: str, value: Any) -> None:
|
|
72
|
+
self.attributes[name] = value
|
|
73
|
+
|
|
74
|
+
def set_attributes(self, attributes: dict[str, Any]) -> None:
|
|
75
|
+
self.attributes.update(attributes)
|
|
76
|
+
|
|
77
|
+
def set_status(self, status: StatusCode) -> None:
|
|
78
|
+
self.status = status
|
|
79
|
+
|
|
80
|
+
def record_exception(self, error: Exception) -> None:
|
|
81
|
+
self.error = error
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import functools
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from collections.abc import Awaitable
|
|
6
|
+
from typing import Any, Callable, ParamSpec, TypeVar
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _datetime_to_span_time(dt: datetime.datetime) -> int:
|
|
14
|
+
if dt.tzinfo is None or dt.tzinfo.utcoffset(dt) is None:
|
|
15
|
+
dt = dt.replace(tzinfo=datetime.timezone.utc)
|
|
16
|
+
|
|
17
|
+
return int(dt.timestamp() * 1_000_000_000)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _unpack_object(obj: dict[str, Any] | list[Any] | BaseModel, prefix: str = "") -> dict[str, Any]:
|
|
21
|
+
if not isinstance(obj, dict) and not isinstance(obj, list):
|
|
22
|
+
obj_ref = obj
|
|
23
|
+
obj = json.loads(stringify(obj))
|
|
24
|
+
if not isinstance(obj, dict) and not isinstance(obj, list):
|
|
25
|
+
logger.warning(f"Cannot unpack object of type {type(obj_ref)} (prefix={prefix})")
|
|
26
|
+
return {"value": str(obj)}
|
|
27
|
+
|
|
28
|
+
if prefix and prefix.startswith("."):
|
|
29
|
+
prefix = prefix[1:]
|
|
30
|
+
if prefix and not prefix.endswith("."):
|
|
31
|
+
prefix += "."
|
|
32
|
+
|
|
33
|
+
output = {}
|
|
34
|
+
for key, value in obj.items() if isinstance(obj, dict) else enumerate(obj):
|
|
35
|
+
if value is None:
|
|
36
|
+
continue
|
|
37
|
+
if is_primitive(value):
|
|
38
|
+
output[f"{prefix}{key}"] = str(value)
|
|
39
|
+
else:
|
|
40
|
+
output.update(_unpack_object(value, prefix=f"{prefix}{key}"))
|
|
41
|
+
return output
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_primitive(value: Any) -> bool:
|
|
45
|
+
return isinstance(value, str | bool | int | float | type(None))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def stringify(value: Any, pretty: bool = False) -> str:
|
|
49
|
+
if is_primitive(value):
|
|
50
|
+
return str(value)
|
|
51
|
+
|
|
52
|
+
from beeai_framework.utils.strings import to_json
|
|
53
|
+
|
|
54
|
+
return to_json(value, sort_keys=False, indent=4 if pretty else None)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
T = TypeVar("T")
|
|
58
|
+
P = ParamSpec("P")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def exception_handler(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T | None]]:
|
|
62
|
+
@functools.wraps(func)
|
|
63
|
+
async def wrapped(*args: P.args, **kwargs: P.kwargs) -> T | None:
|
|
64
|
+
try:
|
|
65
|
+
return await func(*args, **kwargs)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error("Error has occurred in the telemetry package.", exc_info=e)
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
return wrapped
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def safe_dump_model_schema(model: type[BaseModel]) -> dict[str, Any]:
|
|
74
|
+
try:
|
|
75
|
+
return model.model_json_schema(mode="serialization")
|
|
76
|
+
except: # noqa: E722
|
|
77
|
+
return {}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import ClassVar
|
|
2
|
+
|
|
3
|
+
from beeai_framework.agents import BaseAgent
|
|
4
|
+
from beeai_framework.context import RunContext, RunContextStartEvent
|
|
5
|
+
from beeai_framework.emitter import EventMeta
|
|
6
|
+
|
|
7
|
+
from openinference.instrumentation.beeai._utils import _unpack_object
|
|
8
|
+
from openinference.instrumentation.beeai.processors.base import Processor
|
|
9
|
+
from openinference.semconv.trace import (
|
|
10
|
+
OpenInferenceSpanKindValues,
|
|
11
|
+
SpanAttributes,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AgentProcessor(Processor):
|
|
16
|
+
kind: ClassVar[OpenInferenceSpanKindValues] = OpenInferenceSpanKindValues.AGENT
|
|
17
|
+
|
|
18
|
+
def __init__(self, event: RunContextStartEvent, meta: "EventMeta") -> None:
|
|
19
|
+
super().__init__(event, meta)
|
|
20
|
+
|
|
21
|
+
assert isinstance(meta.creator, RunContext)
|
|
22
|
+
assert isinstance(meta.creator.instance, BaseAgent)
|
|
23
|
+
|
|
24
|
+
agent = meta.creator.instance
|
|
25
|
+
self.span.name = agent.meta.name or self.span.name
|
|
26
|
+
self.span.set_attributes(
|
|
27
|
+
{
|
|
28
|
+
SpanAttributes.AGENT_NAME: agent.meta.name,
|
|
29
|
+
f"{SpanAttributes.METADATA}.agent_description": agent.meta.description,
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
self.span.set_attributes(
|
|
33
|
+
_unpack_object(agent.memory.to_json_safe(), prefix=f"{SpanAttributes.METADATA}.memory")
|
|
34
|
+
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from beeai_framework.context import RunContextStartEvent
|
|
4
|
+
from beeai_framework.emitter import EventMeta
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
7
|
+
from openinference.instrumentation.beeai._utils import _unpack_object, stringify
|
|
8
|
+
from openinference.instrumentation.beeai.processors.agents.base import AgentProcessor
|
|
9
|
+
from openinference.semconv.trace import OpenInferenceMimeTypeValues, SpanAttributes
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ReActAgentProcessor(AgentProcessor):
|
|
13
|
+
def __init__(self, event: "RunContextStartEvent", meta: "EventMeta"):
|
|
14
|
+
super().__init__(event, meta)
|
|
15
|
+
self._last_chunk_index = 0
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
async def update(
|
|
19
|
+
self,
|
|
20
|
+
event: Any,
|
|
21
|
+
meta: "EventMeta",
|
|
22
|
+
) -> None:
|
|
23
|
+
from beeai_framework.agents.react import (
|
|
24
|
+
ReActAgentErrorEvent,
|
|
25
|
+
ReActAgentRetryEvent,
|
|
26
|
+
ReActAgentStartEvent,
|
|
27
|
+
ReActAgentSuccessEvent,
|
|
28
|
+
ReActAgentUpdateEvent,
|
|
29
|
+
)
|
|
30
|
+
from beeai_framework.backend import UserMessage
|
|
31
|
+
|
|
32
|
+
await super().update(event, meta)
|
|
33
|
+
|
|
34
|
+
self.span.add_event(f"{meta.name} ({meta.path})", timestamp=meta.created_at)
|
|
35
|
+
|
|
36
|
+
match event:
|
|
37
|
+
case ReActAgentStartEvent():
|
|
38
|
+
last_user_message: UserMessage | None = next(
|
|
39
|
+
(
|
|
40
|
+
msg
|
|
41
|
+
for msg in reversed(event.memory.messages)
|
|
42
|
+
if isinstance(msg, UserMessage)
|
|
43
|
+
),
|
|
44
|
+
None,
|
|
45
|
+
)
|
|
46
|
+
self.span.set_attributes(
|
|
47
|
+
{
|
|
48
|
+
SpanAttributes.INPUT_VALUE: last_user_message.text
|
|
49
|
+
if last_user_message
|
|
50
|
+
else "",
|
|
51
|
+
SpanAttributes.INPUT_MIME_TYPE: OpenInferenceMimeTypeValues.TEXT.value,
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
case ReActAgentUpdateEvent():
|
|
55
|
+
value = event.update.parsed_value or event.update.value
|
|
56
|
+
self.span.set_attribute(
|
|
57
|
+
f"{SpanAttributes.METADATA}.iteration", event.meta.iteration
|
|
58
|
+
)
|
|
59
|
+
self.span.set_attributes(
|
|
60
|
+
_unpack_object(
|
|
61
|
+
{event.update.key: stringify(value)},
|
|
62
|
+
prefix=f"{SpanAttributes.METADATA}.iterations.{event.meta.iteration}",
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
case ReActAgentErrorEvent():
|
|
67
|
+
span = self.span.child(meta.name, event=(event, meta))
|
|
68
|
+
span.record_exception(event.error)
|
|
69
|
+
case ReActAgentRetryEvent():
|
|
70
|
+
self.span.child(meta.name, event=(event, meta))
|
|
71
|
+
case ReActAgentSuccessEvent():
|
|
72
|
+
self.span.set_attribute(SpanAttributes.OUTPUT_VALUE, event.data.text)
|
|
73
|
+
self.span.set_attribute(
|
|
74
|
+
SpanAttributes.OUTPUT_MIME_TYPE, OpenInferenceMimeTypeValues.TEXT.value
|
|
75
|
+
)
|
|
76
|
+
case _:
|
|
77
|
+
self.span.child(meta.name, event=(event, meta))
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from beeai_framework.agents.experimental.events import (
|
|
4
|
+
RequirementAgentStartEvent,
|
|
5
|
+
RequirementAgentSuccessEvent,
|
|
6
|
+
)
|
|
7
|
+
from beeai_framework.agents.experimental.types import RequirementAgentRunStateStep
|
|
8
|
+
from beeai_framework.context import RunContextStartEvent
|
|
9
|
+
from beeai_framework.emitter import EventMeta
|
|
10
|
+
from typing_extensions import override
|
|
11
|
+
|
|
12
|
+
from openinference.instrumentation.beeai._utils import _unpack_object
|
|
13
|
+
from openinference.instrumentation.beeai.processors.agents.base import AgentProcessor
|
|
14
|
+
from openinference.semconv.trace import OpenInferenceMimeTypeValues, SpanAttributes
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RequirementAgentProcessor(AgentProcessor):
|
|
18
|
+
def __init__(self, event: "RunContextStartEvent", meta: "EventMeta"):
|
|
19
|
+
super().__init__(event, meta)
|
|
20
|
+
self._steps: list[RequirementAgentRunStateStep] = []
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
async def update(
|
|
24
|
+
self,
|
|
25
|
+
event: Any,
|
|
26
|
+
meta: "EventMeta",
|
|
27
|
+
) -> None:
|
|
28
|
+
await super().update(event, meta)
|
|
29
|
+
self.span.add_event(f"{meta.name} ({meta.path})", timestamp=meta.created_at)
|
|
30
|
+
|
|
31
|
+
match event:
|
|
32
|
+
case RequirementAgentStartEvent():
|
|
33
|
+
self._sync_steps(event.state.steps)
|
|
34
|
+
case RequirementAgentSuccessEvent():
|
|
35
|
+
self._sync_steps(event.state.steps)
|
|
36
|
+
|
|
37
|
+
if event.state.answer is not None:
|
|
38
|
+
self.span.set_attributes(
|
|
39
|
+
{
|
|
40
|
+
SpanAttributes.OUTPUT_VALUE: event.state.answer.text,
|
|
41
|
+
SpanAttributes.OUTPUT_MIME_TYPE: OpenInferenceMimeTypeValues.TEXT.value,
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
if event.state.result is not None:
|
|
45
|
+
self.span.set_attributes(
|
|
46
|
+
_unpack_object(
|
|
47
|
+
event.state.result, prefix=f"{SpanAttributes.METADATA}.result"
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
case _:
|
|
51
|
+
self.span.child(meta.name, event=(event, meta))
|
|
52
|
+
|
|
53
|
+
def _sync_steps(self, steps: list["RequirementAgentRunStateStep"]) -> None:
|
|
54
|
+
new_items = steps[len(self._steps) :]
|
|
55
|
+
self._steps.extend(new_items)
|
|
56
|
+
|
|
57
|
+
for idx, (new_step, old_step) in enumerate(zip(steps, self._steps)):
|
|
58
|
+
if new_step.id != old_step.id:
|
|
59
|
+
# TODO: cleanup old keys
|
|
60
|
+
self._steps.clear()
|
|
61
|
+
self._sync_steps(steps)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
if new_step in new_items or new_step != old_step:
|
|
65
|
+
self._steps[idx] = new_step
|
|
66
|
+
self.span.set_attributes(
|
|
67
|
+
_unpack_object(
|
|
68
|
+
new_step,
|
|
69
|
+
prefix=f"{SpanAttributes.METADATA}.steps.{idx}",
|
|
70
|
+
)
|
|
71
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from beeai_framework.agents.tool_calling import (
|
|
4
|
+
ToolCallingAgentStartEvent,
|
|
5
|
+
ToolCallingAgentSuccessEvent,
|
|
6
|
+
)
|
|
7
|
+
from beeai_framework.emitter import EventMeta
|
|
8
|
+
from typing_extensions import override
|
|
9
|
+
|
|
10
|
+
from openinference.instrumentation.beeai.processors.agents.base import AgentProcessor
|
|
11
|
+
from openinference.semconv.trace import OpenInferenceMimeTypeValues, SpanAttributes
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ToolCallingAgentProcessor(AgentProcessor):
|
|
15
|
+
@override
|
|
16
|
+
async def update(
|
|
17
|
+
self,
|
|
18
|
+
event: Any,
|
|
19
|
+
meta: "EventMeta",
|
|
20
|
+
) -> None:
|
|
21
|
+
await super().update(event, meta)
|
|
22
|
+
self.span.add_event(f"{meta.name} ({meta.path})", timestamp=meta.created_at)
|
|
23
|
+
|
|
24
|
+
match event:
|
|
25
|
+
case ToolCallingAgentStartEvent():
|
|
26
|
+
pass
|
|
27
|
+
case ToolCallingAgentSuccessEvent():
|
|
28
|
+
if event.state.result is not None:
|
|
29
|
+
self.span.set_attribute(SpanAttributes.OUTPUT_VALUE, event.state.result.text)
|
|
30
|
+
self.span.set_attribute(
|
|
31
|
+
SpanAttributes.OUTPUT_MIME_TYPE, OpenInferenceMimeTypeValues.TEXT.value
|
|
32
|
+
)
|
|
33
|
+
case _:
|
|
34
|
+
self.span.child(meta.name, event=(event, meta))
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from beeai_framework.context import RunContextFinishEvent, RunContextStartEvent
|
|
5
|
+
from beeai_framework.emitter import EventMeta
|
|
6
|
+
|
|
7
|
+
from opentelemetry.trace import StatusCode
|
|
8
|
+
|
|
9
|
+
from openinference.instrumentation.beeai._span import SpanWrapper
|
|
10
|
+
from openinference.instrumentation.beeai._utils import stringify
|
|
11
|
+
from openinference.semconv.trace import (
|
|
12
|
+
OpenInferenceMimeTypeValues,
|
|
13
|
+
OpenInferenceSpanKindValues,
|
|
14
|
+
SpanAttributes,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Processor:
|
|
19
|
+
kind: ClassVar[OpenInferenceSpanKindValues] = OpenInferenceSpanKindValues.UNKNOWN
|
|
20
|
+
|
|
21
|
+
def __init__(self, event: "RunContextStartEvent", meta: "EventMeta"):
|
|
22
|
+
from beeai_framework.context import RunContext
|
|
23
|
+
|
|
24
|
+
assert isinstance(meta.creator, RunContext)
|
|
25
|
+
target_cls = type(meta.creator.instance)
|
|
26
|
+
|
|
27
|
+
self.span = SpanWrapper(name=target_cls.__name__, kind=type(self).kind)
|
|
28
|
+
self.span.started_at = meta.created_at
|
|
29
|
+
self.span.attributes.update(
|
|
30
|
+
{
|
|
31
|
+
SpanAttributes.OPENINFERENCE_SPAN_KIND: type(self).kind,
|
|
32
|
+
SpanAttributes.INPUT_VALUE: stringify(event.input),
|
|
33
|
+
SpanAttributes.INPUT_MIME_TYPE: OpenInferenceMimeTypeValues.JSON.value,
|
|
34
|
+
f"{SpanAttributes.METADATA}.class_name": target_cls.__name__,
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
async def update(
|
|
39
|
+
self,
|
|
40
|
+
event: Any,
|
|
41
|
+
meta: "EventMeta",
|
|
42
|
+
) -> None:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
async def end(self, event: "RunContextFinishEvent", meta: "EventMeta") -> None:
|
|
46
|
+
if event.output is not None:
|
|
47
|
+
if SpanAttributes.OUTPUT_VALUE not in self.span.attributes:
|
|
48
|
+
self.span.attributes.update(
|
|
49
|
+
{
|
|
50
|
+
SpanAttributes.OUTPUT_VALUE: stringify(event.output),
|
|
51
|
+
SpanAttributes.OUTPUT_MIME_TYPE: OpenInferenceMimeTypeValues.JSON.value,
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
self.span.set_status(StatusCode.OK)
|
|
55
|
+
|
|
56
|
+
if event.error is not None:
|
|
57
|
+
self.span.set_status(StatusCode.ERROR)
|
|
58
|
+
self.span.record_exception(event.error)
|
|
59
|
+
|
|
60
|
+
self.span.ended_at = meta.created_at
|