hivetrace 1.3.0__py3-none-any.whl → 1.3.2__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.
- hivetrace/__init__.py +24 -8
- hivetrace/adapters/__init__.py +30 -5
- hivetrace/adapters/crewai/__init__.py +3 -0
- hivetrace/adapters/langchain/__init__.py +18 -0
- hivetrace/adapters/langchain/adapter.py +269 -0
- hivetrace/adapters/langchain/behavior_tracker.py +61 -0
- hivetrace/adapters/langchain/callback.py +544 -0
- hivetrace/adapters/langchain/decorators.py +104 -0
- hivetrace/adapters/langchain/models.py +59 -0
- {hivetrace-1.3.0.dist-info → hivetrace-1.3.2.dist-info}/METADATA +198 -22
- hivetrace-1.3.2.dist-info/RECORD +26 -0
- {hivetrace-1.3.0.dist-info → hivetrace-1.3.2.dist-info}/WHEEL +1 -1
- hivetrace-1.3.0.dist-info/RECORD +0 -20
- {hivetrace-1.3.0.dist-info → hivetrace-1.3.2.dist-info}/LICENSE +0 -0
- {hivetrace-1.3.0.dist-info → hivetrace-1.3.2.dist-info}/top_level.txt +0 -0
hivetrace/__init__.py
CHANGED
|
@@ -5,16 +5,32 @@ from .hivetrace import (
|
|
|
5
5
|
UnauthorizedError,
|
|
6
6
|
)
|
|
7
7
|
|
|
8
|
+
__all__ = [
|
|
9
|
+
"HivetraceSDK",
|
|
10
|
+
"InvalidParameterError",
|
|
11
|
+
"MissingConfigError",
|
|
12
|
+
"UnauthorizedError",
|
|
13
|
+
]
|
|
14
|
+
|
|
8
15
|
try:
|
|
9
|
-
from hivetrace.crewai_adapter import CrewAIAdapter
|
|
16
|
+
from hivetrace.crewai_adapter import CrewAIAdapter as _CrewAIAdapter
|
|
17
|
+
from hivetrace.crewai_adapter import trace as _crewai_trace
|
|
18
|
+
|
|
19
|
+
CrewAIAdapter = _CrewAIAdapter
|
|
20
|
+
crewai_trace = _crewai_trace
|
|
21
|
+
trace = _crewai_trace
|
|
10
22
|
|
|
11
|
-
__all__
|
|
23
|
+
__all__.extend(["CrewAIAdapter", "crewai_trace", "trace"])
|
|
24
|
+
except ImportError:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
from hivetrace.adapters.langchain import LangChainAdapter as _LangChainAdapter
|
|
29
|
+
from hivetrace.adapters.langchain import trace as _langchain_trace
|
|
12
30
|
|
|
31
|
+
LangChainAdapter = _LangChainAdapter
|
|
32
|
+
langchain_trace = _langchain_trace
|
|
13
33
|
|
|
34
|
+
__all__.extend(["LangChainAdapter", "langchain_trace"])
|
|
14
35
|
except ImportError:
|
|
15
|
-
|
|
16
|
-
"HivetraceSDK",
|
|
17
|
-
"InvalidParameterError",
|
|
18
|
-
"MissingConfigError",
|
|
19
|
-
"UnauthorizedError",
|
|
20
|
-
]
|
|
36
|
+
pass
|
hivetrace/adapters/__init__.py
CHANGED
|
@@ -1,7 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
Adapters package initialization.
|
|
3
|
-
"""
|
|
1
|
+
__all__ = []
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
try:
|
|
4
|
+
from hivetrace.adapters.crewai import (
|
|
5
|
+
CrewAIAdapter as _CrewAIAdapter,
|
|
6
|
+
)
|
|
7
|
+
from hivetrace.adapters.crewai import (
|
|
8
|
+
trace as _crewai_trace,
|
|
9
|
+
)
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
CrewAIAdapter = _CrewAIAdapter
|
|
12
|
+
crewai_trace = _crewai_trace
|
|
13
|
+
trace = _crewai_trace
|
|
14
|
+
|
|
15
|
+
__all__.extend(["CrewAIAdapter", "crewai_trace", "trace"])
|
|
16
|
+
except ImportError:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from hivetrace.adapters.langchain import (
|
|
21
|
+
LangChainAdapter as _LangChainAdapter,
|
|
22
|
+
)
|
|
23
|
+
from hivetrace.adapters.langchain import (
|
|
24
|
+
trace as _langchain_trace,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
LangChainAdapter = _LangChainAdapter
|
|
28
|
+
langchain_trace = _langchain_trace
|
|
29
|
+
|
|
30
|
+
__all__.extend(["LangChainAdapter", "langchain_trace"])
|
|
31
|
+
except ImportError:
|
|
32
|
+
pass
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .adapter import LangChainAdapter
|
|
2
|
+
from .behavior_tracker import AgentBehaviorTracker
|
|
3
|
+
from .callback import AgentLoggingCallback
|
|
4
|
+
from .decorators import trace
|
|
5
|
+
from .models import AgentInfo, AgentStats, ToolCallInfo
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"AgentLoggingCallback",
|
|
9
|
+
"AgentBehaviorTracker",
|
|
10
|
+
"AgentStats",
|
|
11
|
+
"AgentInfo",
|
|
12
|
+
"ToolCallInfo",
|
|
13
|
+
"LangChainAdapter",
|
|
14
|
+
"trace",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
langchain_trace = trace
|
|
18
|
+
__all__.append("langchain_trace")
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional, TypeVar
|
|
2
|
+
|
|
3
|
+
from hivetrace.adapters.base_adapter import BaseAdapter
|
|
4
|
+
from hivetrace.adapters.langchain.callback import AgentLoggingCallback
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LangChainAdapter(BaseAdapter):
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
hivetrace,
|
|
13
|
+
application_id: str,
|
|
14
|
+
user_id: Optional[str] = None,
|
|
15
|
+
session_id: Optional[str] = None,
|
|
16
|
+
) -> None:
|
|
17
|
+
super().__init__(hivetrace, application_id, user_id, session_id)
|
|
18
|
+
self._forced_agent_conversation_id: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
def output(
|
|
21
|
+
self, message: str, additional_params: Optional[Dict[str, Any]] = None
|
|
22
|
+
) -> None:
|
|
23
|
+
self._prepare_and_log(
|
|
24
|
+
"output",
|
|
25
|
+
self.async_mode,
|
|
26
|
+
message_content=message,
|
|
27
|
+
additional_params_from_caller=additional_params,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def function_call_callback(self, tool_call_details: Dict[str, Any]) -> None:
|
|
31
|
+
self._prepare_and_log(
|
|
32
|
+
"function_call",
|
|
33
|
+
self.async_mode,
|
|
34
|
+
tool_call_details=tool_call_details,
|
|
35
|
+
additional_params_from_caller=tool_call_details,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def _get_conversation_id(self, callback: Any) -> Optional[str]:
|
|
39
|
+
if self._forced_agent_conversation_id:
|
|
40
|
+
return self._forced_agent_conversation_id
|
|
41
|
+
|
|
42
|
+
root_name: Optional[str] = getattr(callback, "default_root_name", None)
|
|
43
|
+
if not root_name or not hasattr(callback, "agents_log"):
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
agents_log: Dict[str, Any] = getattr(callback, "agents_log", {})
|
|
47
|
+
if root_name not in agents_log:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
return agents_log[root_name].get("agent_info", {}).get("id")
|
|
51
|
+
|
|
52
|
+
def _get_message_for_output(self, agent_info: Dict[str, Any]) -> str:
|
|
53
|
+
return str(
|
|
54
|
+
agent_info.get("agent_response")
|
|
55
|
+
or agent_info.get("agent_answer")
|
|
56
|
+
or agent_info.get("tool_response")
|
|
57
|
+
or agent_info.get("message")
|
|
58
|
+
or ""
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def send_log_data(self, callback: Any) -> None:
|
|
62
|
+
if not hasattr(callback, "agents_log"):
|
|
63
|
+
print("[LangChainAdapter] У переданного callback нет поля 'agents_log'.")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
agents_log: Dict[str, Any] = getattr(callback, "agents_log") or {}
|
|
67
|
+
agent_conversation_id = self._get_conversation_id(callback)
|
|
68
|
+
|
|
69
|
+
for agent_name, agent_entry in agents_log.items():
|
|
70
|
+
agent_info: Dict[str, Any] = agent_entry.get("agent_info", {})
|
|
71
|
+
if not agent_info:
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
message_for_output = self._get_message_for_output(agent_info)
|
|
75
|
+
additional_params = self._build_additional_params(
|
|
76
|
+
agent_name,
|
|
77
|
+
agent_info,
|
|
78
|
+
agent_conversation_id=agent_conversation_id,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self._prepare_and_log(
|
|
82
|
+
"output",
|
|
83
|
+
self.async_mode,
|
|
84
|
+
message_content=message_for_output,
|
|
85
|
+
additional_params_from_caller=additional_params,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
self._process_tool_calls(
|
|
89
|
+
agent_entry, agent_name, agent_info, agent_conversation_id
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def _process_tool_calls(
|
|
93
|
+
self,
|
|
94
|
+
agent_entry: Dict[str, Any],
|
|
95
|
+
agent_name: str,
|
|
96
|
+
agent_info: Dict[str, Any],
|
|
97
|
+
agent_conversation_id: Optional[str],
|
|
98
|
+
) -> None:
|
|
99
|
+
for tool_call in agent_entry.get("tool_call_info", []):
|
|
100
|
+
tool_call_details = {
|
|
101
|
+
"tool_call_id": tool_call.get("id"),
|
|
102
|
+
"func_name": tool_call.get("tool"),
|
|
103
|
+
"func_args": tool_call.get("tool_input"),
|
|
104
|
+
"func_result": tool_call.get("tool_response")
|
|
105
|
+
or tool_call.get("tool_answer"),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func_call_additional = {
|
|
109
|
+
"agent_parent_id": tool_call.get("agent_parent_id"),
|
|
110
|
+
"agents": {
|
|
111
|
+
tool_call.get("agent_id", ""): {
|
|
112
|
+
"name": tool_call.get("agent"),
|
|
113
|
+
"description": "",
|
|
114
|
+
"agent_parent_id": tool_call.get("parent"),
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if agent_conversation_id:
|
|
120
|
+
func_call_additional["agent_conversation_id"] = agent_conversation_id
|
|
121
|
+
|
|
122
|
+
self._prepare_and_log(
|
|
123
|
+
"function_call",
|
|
124
|
+
self.async_mode,
|
|
125
|
+
tool_call_details=tool_call_details,
|
|
126
|
+
additional_params_from_caller=func_call_additional,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
class _HiveTraceLoggingCallback(AgentLoggingCallback):
|
|
130
|
+
def __init__(self, adapter: "LangChainAdapter", *args: Any, **kwargs: Any):
|
|
131
|
+
super().__init__(*args, **kwargs)
|
|
132
|
+
self._adapter = adapter
|
|
133
|
+
|
|
134
|
+
def _get_conversation_id(self) -> Optional[str]:
|
|
135
|
+
if not (hasattr(self, "default_root_name") and hasattr(self, "agents_log")):
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
if self.default_root_name not in self.agents_log:
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
self.agents_log[self.default_root_name].get("agent_info", {}).get("id")
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def _flush_agent_output(self, agent_name: str) -> None:
|
|
146
|
+
agent_entry = self.agents_log.get(agent_name)
|
|
147
|
+
if not agent_entry:
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
agent_info = agent_entry.get("agent_info", {})
|
|
151
|
+
message_for_output = self._adapter._get_message_for_output(agent_info)
|
|
152
|
+
if not message_for_output:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
conversation_id = self._get_conversation_id()
|
|
156
|
+
additional_params = self._adapter._build_additional_params(
|
|
157
|
+
agent_name,
|
|
158
|
+
agent_info,
|
|
159
|
+
agent_conversation_id=conversation_id,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
self._adapter._prepare_and_log(
|
|
163
|
+
"output",
|
|
164
|
+
self._adapter.async_mode,
|
|
165
|
+
message_content=message_for_output,
|
|
166
|
+
additional_params_from_caller=additional_params,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def _flush_tool_call(self, agent_name: str) -> None:
|
|
170
|
+
agent_entry = self.agents_log.get(agent_name)
|
|
171
|
+
if not agent_entry:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
for tool_call in agent_entry.get("tool_call_info", []):
|
|
175
|
+
if tool_call.get("status") != "completed" or tool_call.get(
|
|
176
|
+
"_sent_to_hivetrace"
|
|
177
|
+
):
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
tool_call_details = {
|
|
181
|
+
"tool_call_id": tool_call.get("id"),
|
|
182
|
+
"func_name": tool_call.get("tool"),
|
|
183
|
+
"func_args": tool_call.get("tool_input"),
|
|
184
|
+
"func_result": tool_call.get("tool_response")
|
|
185
|
+
or tool_call.get("tool_answer"),
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
agent_info = agent_entry.get("agent_info", {})
|
|
189
|
+
conversation_id = self._get_conversation_id()
|
|
190
|
+
|
|
191
|
+
additional_params = self._adapter._build_additional_params(
|
|
192
|
+
agent_name,
|
|
193
|
+
agent_info,
|
|
194
|
+
agent_conversation_id=conversation_id,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if tool_call.get("agent_parent_id") is not None:
|
|
198
|
+
additional_params["agent_parent_id"] = tool_call.get(
|
|
199
|
+
"agent_parent_id"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
self._adapter._prepare_and_log(
|
|
203
|
+
"function_call",
|
|
204
|
+
self._adapter.async_mode,
|
|
205
|
+
tool_call_details=tool_call_details,
|
|
206
|
+
additional_params_from_caller=additional_params,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
tool_call["_sent_to_hivetrace"] = True
|
|
210
|
+
|
|
211
|
+
def on_tool_end(self, *args: Any, **kwargs: Any) -> None:
|
|
212
|
+
super().on_tool_end(*args, **kwargs)
|
|
213
|
+
run_id = kwargs.get("run_id")
|
|
214
|
+
agent_name = self.current_agent_run_ids.get(str(run_id))
|
|
215
|
+
if agent_name:
|
|
216
|
+
self._flush_tool_call(agent_name)
|
|
217
|
+
|
|
218
|
+
def on_agent_finish(self, finish: Any, *args: Any, **kwargs: Any) -> None:
|
|
219
|
+
run_id = kwargs.get("run_id")
|
|
220
|
+
super().on_agent_finish(finish, *args, **kwargs)
|
|
221
|
+
|
|
222
|
+
agent_name = self.current_agent_run_ids.get(str(run_id))
|
|
223
|
+
if agent_name:
|
|
224
|
+
self._flush_agent_output(agent_name)
|
|
225
|
+
self._flush_tool_call(agent_name)
|
|
226
|
+
|
|
227
|
+
def create_logging_callback(
|
|
228
|
+
self, **callback_kwargs: Any
|
|
229
|
+
) -> "_HiveTraceLoggingCallback":
|
|
230
|
+
return LangChainAdapter._HiveTraceLoggingCallback(self, **callback_kwargs)
|
|
231
|
+
|
|
232
|
+
def _build_additional_params(
|
|
233
|
+
self,
|
|
234
|
+
agent_name: str,
|
|
235
|
+
agent_info: Dict[str, Any],
|
|
236
|
+
agent_conversation_id: Optional[str] = None,
|
|
237
|
+
) -> Dict[str, Any]:
|
|
238
|
+
agent_id = agent_info.get("id")
|
|
239
|
+
if agent_id is None:
|
|
240
|
+
return {"agent_info": agent_info}
|
|
241
|
+
|
|
242
|
+
agent_entry: Dict[str, Any] = {
|
|
243
|
+
"name": agent_name,
|
|
244
|
+
"description": agent_info.get("description", ""),
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
parent_id_val = agent_info.get("agent_parent_id")
|
|
248
|
+
if parent_id_val is not None:
|
|
249
|
+
agent_entry["agent_parent_id"] = parent_id_val
|
|
250
|
+
|
|
251
|
+
additional_params: Dict[str, Any] = {
|
|
252
|
+
"agents": {agent_id: agent_entry},
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
effective_conversation_id = (
|
|
256
|
+
agent_conversation_id or self._forced_agent_conversation_id
|
|
257
|
+
)
|
|
258
|
+
if effective_conversation_id:
|
|
259
|
+
additional_params["agent_conversation_id"] = effective_conversation_id
|
|
260
|
+
|
|
261
|
+
if "is_final_answer" in agent_info:
|
|
262
|
+
additional_params["is_final_answer"] = agent_info["is_final_answer"]
|
|
263
|
+
|
|
264
|
+
if self.user_id is not None:
|
|
265
|
+
additional_params["user_id"] = self.user_id
|
|
266
|
+
if self.session_id is not None:
|
|
267
|
+
additional_params["session_id"] = self.session_id
|
|
268
|
+
|
|
269
|
+
return additional_params
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
from .models import AgentStats
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AgentBehaviorTracker:
|
|
7
|
+
"""Class for tracking agent behavior"""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.agent_behavior: Dict[str, AgentStats] = {}
|
|
11
|
+
self.execution_order: List[str] = []
|
|
12
|
+
self.agent_stack: List[str] = []
|
|
13
|
+
|
|
14
|
+
def update_stats(self, agent_name: str, action: str) -> None:
|
|
15
|
+
"""Updates the agent's statistics"""
|
|
16
|
+
if agent_name not in self.agent_behavior:
|
|
17
|
+
self.agent_behavior[agent_name] = AgentStats(
|
|
18
|
+
calls_made=0,
|
|
19
|
+
calls_received=0,
|
|
20
|
+
unique_children=set(),
|
|
21
|
+
unique_parents=set(),
|
|
22
|
+
depth_level=0,
|
|
23
|
+
call_index=len(self.execution_order),
|
|
24
|
+
is_leaf=True,
|
|
25
|
+
)
|
|
26
|
+
self.execution_order.append(agent_name)
|
|
27
|
+
|
|
28
|
+
stats = self.agent_behavior[agent_name]
|
|
29
|
+
|
|
30
|
+
if action == "start":
|
|
31
|
+
stats.calls_made += 1
|
|
32
|
+
if self.agent_stack:
|
|
33
|
+
parent = self.agent_stack[-1]
|
|
34
|
+
stats.unique_parents.add(parent)
|
|
35
|
+
if parent in self.agent_behavior:
|
|
36
|
+
self.agent_behavior[parent].unique_children.add(agent_name)
|
|
37
|
+
self.agent_behavior[parent].is_leaf = False
|
|
38
|
+
elif action == "end":
|
|
39
|
+
stats.calls_received += 1
|
|
40
|
+
stats.depth_level = len(self.agent_stack)
|
|
41
|
+
|
|
42
|
+
def determine_agent_role(self, agent_name: str) -> str:
|
|
43
|
+
"""Determines the role of an agent based on its behavior"""
|
|
44
|
+
if agent_name not in self.agent_behavior:
|
|
45
|
+
return "processing_node"
|
|
46
|
+
|
|
47
|
+
stats = self.agent_behavior[agent_name]
|
|
48
|
+
|
|
49
|
+
if not stats.unique_parents and not stats.unique_children:
|
|
50
|
+
return "root_node"
|
|
51
|
+
if not stats.unique_children:
|
|
52
|
+
return "leaf_node"
|
|
53
|
+
if len(stats.unique_children) > 2 and len(stats.unique_parents) > 1:
|
|
54
|
+
return "hub_node"
|
|
55
|
+
if len(stats.unique_parents) > 1 and len(stats.unique_children) == 1:
|
|
56
|
+
return "bridge_node"
|
|
57
|
+
if len(stats.unique_children) > 1 and len(stats.unique_parents) == 1:
|
|
58
|
+
return "splitter_node"
|
|
59
|
+
if len(stats.unique_parents) > 1 and len(stats.unique_children) > 1:
|
|
60
|
+
return "collector_node"
|
|
61
|
+
return "processing_node"
|