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 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, trace
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__ = ["CrewAIAdapter", "trace"]
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
- __all__ = [
16
- "HivetraceSDK",
17
- "InvalidParameterError",
18
- "MissingConfigError",
19
- "UnauthorizedError",
20
- ]
36
+ pass
@@ -1,7 +1,32 @@
1
- """
2
- Adapters package initialization.
3
- """
1
+ __all__ = []
4
2
 
5
- from hivetrace.adapters.crewai import CrewAIAdapter, trace
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
- __all__ = ["CrewAIAdapter", "trace"]
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
@@ -6,3 +6,6 @@ from hivetrace.adapters.crewai.adapter import CrewAIAdapter
6
6
  from hivetrace.adapters.crewai.decorators import trace
7
7
 
8
8
  __all__ = ["CrewAIAdapter", "trace"]
9
+
10
+ crewai_trace = trace
11
+ __all__.append("crewai_trace")
@@ -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"