hivetrace 1.3.16__py3-none-any.whl → 1.4.0__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/adapters/base_adapter.py +8 -2
- hivetrace/adapters/crewai/adapter.py +7 -7
- hivetrace/adapters/crewai/decorators.py +3 -0
- hivetrace/adapters/crewai/monitored_agent.py +2 -2
- hivetrace/adapters/crewai/tool_wrapper.py +4 -3
- hivetrace/adapters/openai_agents/adapter.py +37 -11
- hivetrace/adapters/openai_agents/tracing.py +13 -10
- hivetrace/client/async_client.py +17 -54
- hivetrace/client/base.py +28 -48
- hivetrace/client/sync_client.py +16 -57
- hivetrace/errors/api.py +2 -2
- hivetrace/errors/validation.py +1 -1
- hivetrace/handlers/response_builder.py +14 -73
- hivetrace/models/requests.py +21 -28
- hivetrace/models/responses.py +26 -21
- hivetrace/utils/error_helpers.py +25 -3
- {hivetrace-1.3.16.dist-info → hivetrace-1.4.0.dist-info}/METADATA +188 -203
- {hivetrace-1.3.16.dist-info → hivetrace-1.4.0.dist-info}/RECORD +21 -21
- {hivetrace-1.3.16.dist-info → hivetrace-1.4.0.dist-info}/WHEEL +0 -0
- {hivetrace-1.3.16.dist-info → hivetrace-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {hivetrace-1.3.16.dist-info → hivetrace-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Base adapter class for HiveTrace integrations.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import Any, Dict, Optional
|
|
5
|
+
from typing import Any, Coroutine, Dict, Optional, cast
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class BaseAdapter:
|
|
@@ -115,7 +115,13 @@ class BaseAdapter:
|
|
|
115
115
|
maybe_coro = actual_log_method()
|
|
116
116
|
|
|
117
117
|
if inspect.isawaitable(maybe_coro):
|
|
118
|
-
|
|
118
|
+
if inspect.iscoroutine(maybe_coro):
|
|
119
|
+
asyncio.create_task(cast(Coroutine[Any, Any, Any], maybe_coro))
|
|
120
|
+
else:
|
|
121
|
+
async def _await_and_drop() -> None:
|
|
122
|
+
await maybe_coro
|
|
123
|
+
|
|
124
|
+
asyncio.create_task(_await_and_drop())
|
|
119
125
|
else:
|
|
120
126
|
# If the method is unexpectedly sync in async mode, call directly
|
|
121
127
|
# to avoid dropping the log.
|
|
@@ -28,15 +28,15 @@ class CrewAIAdapter(BaseAdapter):
|
|
|
28
28
|
):
|
|
29
29
|
super().__init__(hivetrace, application_id, user_id, session_id)
|
|
30
30
|
self.agent_id_mapping = agent_id_mapping or {}
|
|
31
|
-
self.agents_info = {}
|
|
32
|
-
self._runtime_user_id = None
|
|
33
|
-
self._runtime_session_id = None
|
|
34
|
-
self._runtime_agents_conversation_id = None
|
|
35
|
-
self._current_parent_agent_id = None
|
|
36
|
-
self._last_active_agent_id = None
|
|
31
|
+
self.agents_info: Dict[str, Dict[str, Any]] = {}
|
|
32
|
+
self._runtime_user_id: Optional[str] = None
|
|
33
|
+
self._runtime_session_id: Optional[str] = None
|
|
34
|
+
self._runtime_agents_conversation_id: Optional[str] = None
|
|
35
|
+
self._current_parent_agent_id: Optional[str] = None
|
|
36
|
+
self._last_active_agent_id: Optional[str] = None
|
|
37
37
|
self._conversation_started = False
|
|
38
38
|
self._first_agent_logged = False
|
|
39
|
-
self._recent_messages = []
|
|
39
|
+
self._recent_messages: list[int] = []
|
|
40
40
|
self._max_recent_messages = 5
|
|
41
41
|
|
|
42
42
|
def _reset_conversation_state(self):
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Monitored Agent implementation for CrewAI.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import Any, Callable
|
|
5
|
+
from typing import Any, Callable, Optional
|
|
6
6
|
|
|
7
7
|
from crewai import Agent, Task
|
|
8
8
|
|
|
@@ -37,7 +37,7 @@ class MonitoredAgent(Agent):
|
|
|
37
37
|
self._adapter_instance = adapter_instance
|
|
38
38
|
self.callback_func = callback_func
|
|
39
39
|
self.agent_id = agent_id
|
|
40
|
-
self._last_thought = None
|
|
40
|
+
self._last_thought: Optional[str] = None
|
|
41
41
|
|
|
42
42
|
def execute_task(self, task: Task) -> str:
|
|
43
43
|
"""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Tool wrapping utilities for CrewAI adapter."""
|
|
2
2
|
|
|
3
3
|
import functools
|
|
4
|
-
from typing import Any, Callable
|
|
4
|
+
from typing import Any, Callable, cast
|
|
5
5
|
|
|
6
6
|
from hivetrace.utils.uuid_generator import generate_uuid
|
|
7
7
|
|
|
@@ -58,12 +58,13 @@ def wrap_tool(tool: Any, agent_role: str, adapter_instance) -> Any:
|
|
|
58
58
|
if not (hasattr(tool, "_run") and callable(tool._run)):
|
|
59
59
|
return tool
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
run_callable = cast(Any, tool._run)
|
|
62
|
+
if getattr(run_callable, "_is_hivetrace_wrapped", False):
|
|
62
63
|
return tool
|
|
63
64
|
|
|
64
65
|
tool_name = getattr(tool, "name", "unknown_tool")
|
|
65
66
|
wrapped = wrap_tool_function(tool._run, tool_name, agent_role, adapter_instance)
|
|
66
|
-
wrapped
|
|
67
|
+
setattr(cast(Any, wrapped), "_is_hivetrace_wrapped", True)
|
|
67
68
|
tool._run = wrapped
|
|
68
69
|
|
|
69
70
|
return tool
|
|
@@ -11,21 +11,33 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
11
11
|
def log_traces(self, trace_calls: dict[str, Call | None], conversation_uuid: str):
|
|
12
12
|
_trace_calls = self._join_handoff_spans(trace_calls)
|
|
13
13
|
_trace_calls = self._join_agent_calling_tool_spans(_trace_calls)
|
|
14
|
-
source_agent =
|
|
14
|
+
source_agent: AgentCall | None = None
|
|
15
|
+
for call in _trace_calls.values():
|
|
16
|
+
if isinstance(call, AgentCall):
|
|
17
|
+
source_agent = call
|
|
18
|
+
break
|
|
19
|
+
if source_agent is None:
|
|
20
|
+
return
|
|
21
|
+
|
|
15
22
|
self._log_start_message(source_agent, conversation_uuid)
|
|
16
23
|
|
|
17
24
|
for trace_call in _trace_calls.values():
|
|
18
25
|
if trace_call is None or trace_call.span_parent_id is None:
|
|
19
26
|
continue
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
if
|
|
28
|
+
parent = _trace_calls.get(trace_call.span_parent_id)
|
|
29
|
+
if not isinstance(parent, AgentCall):
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
if isinstance(trace_call, AgentCall):
|
|
33
|
+
if trace_call.output is None:
|
|
34
|
+
continue
|
|
23
35
|
additional_params = {
|
|
24
36
|
"agent_conversation_id": conversation_uuid,
|
|
25
37
|
"is_final_answer": False,
|
|
26
38
|
"agents": {
|
|
27
39
|
trace_call.agent_uuid: {
|
|
28
|
-
"agent_parent_id":
|
|
40
|
+
"agent_parent_id": parent.agent_uuid,
|
|
29
41
|
"name": trace_call.name,
|
|
30
42
|
"description": trace_call.instructions,
|
|
31
43
|
},
|
|
@@ -37,6 +49,8 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
37
49
|
)
|
|
38
50
|
|
|
39
51
|
elif trace_call.type == "tool":
|
|
52
|
+
if trace_call.output is None:
|
|
53
|
+
continue
|
|
40
54
|
self._prepare_and_log(
|
|
41
55
|
log_method_name_stem="function_call",
|
|
42
56
|
is_async=False,
|
|
@@ -49,9 +63,9 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
49
63
|
"additional_parameters": {
|
|
50
64
|
"agent_conversation_id": conversation_uuid,
|
|
51
65
|
"agents": {
|
|
52
|
-
|
|
53
|
-
"name":
|
|
54
|
-
"description":
|
|
66
|
+
parent.agent_uuid: {
|
|
67
|
+
"name": parent.name,
|
|
68
|
+
"description": parent.instructions,
|
|
55
69
|
},
|
|
56
70
|
},
|
|
57
71
|
},
|
|
@@ -63,15 +77,19 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
63
77
|
self, trace_calls: dict[str, Call | None]
|
|
64
78
|
) -> dict[str, Call | None]:
|
|
65
79
|
for span_id, span in trace_calls.items():
|
|
80
|
+
if span is None:
|
|
81
|
+
continue
|
|
66
82
|
if span.type == "agent" and span.span_parent_id is not None:
|
|
67
83
|
parent = trace_calls[span.span_parent_id]
|
|
84
|
+
if parent is None:
|
|
85
|
+
continue
|
|
68
86
|
if parent.type == "tool":
|
|
69
87
|
trace_calls[span.span_parent_id] = None
|
|
70
|
-
|
|
71
|
-
|
|
88
|
+
span.span_parent_id = parent.span_parent_id
|
|
89
|
+
span.input = (
|
|
72
90
|
parent.input if span.input is None else span.input
|
|
73
91
|
)
|
|
74
|
-
|
|
92
|
+
span.output = (
|
|
75
93
|
parent.output if span.output is None else span.output
|
|
76
94
|
)
|
|
77
95
|
return trace_calls
|
|
@@ -80,18 +98,22 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
80
98
|
self, trace_calls: dict[str, Call | None]
|
|
81
99
|
) -> dict[str, Call | None]:
|
|
82
100
|
for span in reversed(trace_calls.values()):
|
|
101
|
+
if span is None:
|
|
102
|
+
continue
|
|
83
103
|
if span.type == "handoff" and span.span_parent_id is not None:
|
|
84
104
|
parent = trace_calls[span.span_parent_id]
|
|
85
105
|
child = next(
|
|
86
106
|
(
|
|
87
107
|
call
|
|
88
108
|
for call in trace_calls.values()
|
|
89
|
-
if call.name == span.to_agent
|
|
109
|
+
if call is not None and call.name == span.to_agent
|
|
90
110
|
),
|
|
91
111
|
None,
|
|
92
112
|
)
|
|
93
113
|
if parent is None:
|
|
94
114
|
continue
|
|
115
|
+
if child is None:
|
|
116
|
+
continue
|
|
95
117
|
child.span_parent_id = span.span_parent_id
|
|
96
118
|
if parent.output is None:
|
|
97
119
|
parent.output = child.output
|
|
@@ -100,6 +122,8 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
100
122
|
return trace_calls
|
|
101
123
|
|
|
102
124
|
def _log_start_message(self, trace_call: AgentCall, conversation_uuid: str):
|
|
125
|
+
if trace_call.input is None:
|
|
126
|
+
return
|
|
103
127
|
self.input(
|
|
104
128
|
message=trace_call.input,
|
|
105
129
|
additional_params={
|
|
@@ -114,6 +138,8 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
114
138
|
)
|
|
115
139
|
|
|
116
140
|
def _log_final_message(self, trace_call: AgentCall, conversation_uuid: str):
|
|
141
|
+
if trace_call.output is None:
|
|
142
|
+
return
|
|
117
143
|
self.output(
|
|
118
144
|
message=trace_call.output,
|
|
119
145
|
additional_params={
|
|
@@ -121,8 +121,10 @@ class HivetraceOpenAIAgentProcessor(TracingProcessor):
|
|
|
121
121
|
isinstance(span.span_data, FunctionSpanData)
|
|
122
122
|
and span.span_data.type == "function"
|
|
123
123
|
):
|
|
124
|
-
self._trace_calls
|
|
125
|
-
|
|
124
|
+
call = self._trace_calls.get(span.span_id)
|
|
125
|
+
if call is not None:
|
|
126
|
+
call.input = span.span_data.input
|
|
127
|
+
call.output = span.span_data.output
|
|
126
128
|
|
|
127
129
|
# Save input and output for agent
|
|
128
130
|
elif (
|
|
@@ -133,15 +135,16 @@ class HivetraceOpenAIAgentProcessor(TracingProcessor):
|
|
|
133
135
|
if not response or not response.output:
|
|
134
136
|
return
|
|
135
137
|
if isinstance(response.output[0], ResponseOutputMessage):
|
|
136
|
-
self._trace_calls
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
self._trace_calls[span.parent_id].instructions = response.instructions
|
|
138
|
+
parent_call = self._trace_calls.get(span.parent_id)
|
|
139
|
+
if parent_call is None:
|
|
140
|
+
return
|
|
141
|
+
parent_call.input = span.span_data.input[0]["content"]
|
|
142
|
+
parent_call.output = response.output[0].content[0].text
|
|
143
|
+
parent_call.instructions = response.instructions
|
|
143
144
|
elif isinstance(response.output[0], ResponseFunctionToolCall):
|
|
144
|
-
self._trace_calls
|
|
145
|
+
parent_call = self._trace_calls.get(span.parent_id)
|
|
146
|
+
if parent_call is not None:
|
|
147
|
+
parent_call.instructions = response.instructions
|
|
145
148
|
|
|
146
149
|
def shutdown(self):
|
|
147
150
|
self._trace_calls = {}
|
hivetrace/client/async_client.py
CHANGED
|
@@ -30,17 +30,17 @@ class AsyncHivetraceSDK(BaseHivetraceSDK):
|
|
|
30
30
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
31
31
|
await self.close()
|
|
32
32
|
|
|
33
|
-
async def
|
|
34
|
-
self,
|
|
33
|
+
async def _post_and_parse(
|
|
34
|
+
self, request_args: Dict[str, Any], endpoint: str
|
|
35
35
|
) -> HivetraceResponse:
|
|
36
|
-
|
|
36
|
+
"""
|
|
37
|
+
Sends a POST request and parses the response.
|
|
38
|
+
"""
|
|
37
39
|
try:
|
|
38
40
|
response = await self.session.post(**request_args)
|
|
39
41
|
response.raise_for_status()
|
|
40
|
-
|
|
41
42
|
api_data = response.json()
|
|
42
|
-
return ResponseBuilder.build_response_from_api(api_data)
|
|
43
|
-
|
|
43
|
+
return ResponseBuilder.build_response_from_api(api_data, endpoint=endpoint)
|
|
44
44
|
except httpx.HTTPStatusError as e:
|
|
45
45
|
return ErrorHandler.handle_http_error(e)
|
|
46
46
|
except httpx.ConnectError as e:
|
|
@@ -54,40 +54,17 @@ class AsyncHivetraceSDK(BaseHivetraceSDK):
|
|
|
54
54
|
except Exception as e:
|
|
55
55
|
return ErrorHandler.handle_unexpected_error(e)
|
|
56
56
|
|
|
57
|
+
async def _send_request(
|
|
58
|
+
self, endpoint: str, payload: Dict[str, Any]
|
|
59
|
+
) -> HivetraceResponse:
|
|
60
|
+
request_args = self._build_request_args(endpoint, payload)
|
|
61
|
+
return await self._post_and_parse(request_args, endpoint)
|
|
62
|
+
|
|
57
63
|
async def _send_files(
|
|
58
64
|
self, endpoint: str, files: List[Tuple[str, bytes, str]]
|
|
59
65
|
) -> HivetraceResponse:
|
|
60
66
|
request_args = self._build_files_request_args(endpoint, files)
|
|
61
|
-
|
|
62
|
-
response = await self.session.post(**request_args)
|
|
63
|
-
response.raise_for_status()
|
|
64
|
-
api_data = response.json()
|
|
65
|
-
return ResponseBuilder.build_response_from_api(api_data)
|
|
66
|
-
except httpx.HTTPStatusError as e:
|
|
67
|
-
return ErrorHandler.handle_http_error(e)
|
|
68
|
-
except httpx.ConnectError as e:
|
|
69
|
-
return ErrorHandler.handle_connection_error(e)
|
|
70
|
-
except httpx.TimeoutException as e:
|
|
71
|
-
return ErrorHandler.handle_timeout_error(e)
|
|
72
|
-
except httpx.RequestError as e:
|
|
73
|
-
return ErrorHandler.handle_request_error(e)
|
|
74
|
-
except ValueError as e:
|
|
75
|
-
return ErrorHandler.handle_json_decode_error(e)
|
|
76
|
-
except Exception as e:
|
|
77
|
-
return ErrorHandler.handle_unexpected_error(e)
|
|
78
|
-
|
|
79
|
-
async def _get_blocking_status(self, endpoint: str) -> Optional[bool]:
|
|
80
|
-
url = f"{self.hivetrace_url}/{endpoint.lstrip('/')}"
|
|
81
|
-
headers = {"Authorization": f"Bearer {self.hivetrace_access_token}"}
|
|
82
|
-
try:
|
|
83
|
-
response = await self.session.get(
|
|
84
|
-
url, headers=headers, timeout=self._DEFAULT_TIMEOUT
|
|
85
|
-
)
|
|
86
|
-
response.raise_for_status()
|
|
87
|
-
data = response.json()
|
|
88
|
-
return data.get("blocked")
|
|
89
|
-
except Exception:
|
|
90
|
-
return None
|
|
67
|
+
return await self._post_and_parse(request_args, endpoint)
|
|
91
68
|
|
|
92
69
|
async def input(
|
|
93
70
|
self,
|
|
@@ -100,22 +77,15 @@ class AsyncHivetraceSDK(BaseHivetraceSDK):
|
|
|
100
77
|
application_id, message, additional_parameters
|
|
101
78
|
)
|
|
102
79
|
process_response = await self._send_request("/process_request/", payload)
|
|
103
|
-
|
|
80
|
+
|
|
104
81
|
analysis_id = self._extract_analysis_id(process_response)
|
|
105
82
|
if analysis_id:
|
|
106
83
|
if files:
|
|
107
84
|
files_response = await self._send_files(
|
|
108
85
|
f"/user_prompt_analysis/{analysis_id}/attach_files", files
|
|
109
86
|
)
|
|
110
|
-
if
|
|
111
|
-
return files_response
|
|
112
|
-
elif isinstance(files_response, dict) and not files_response.get("success", True):
|
|
87
|
+
if self._is_failure_response(files_response):
|
|
113
88
|
return files_response
|
|
114
|
-
|
|
115
|
-
blocked = await self._get_blocking_status(
|
|
116
|
-
f"/user_prompt_analysis/{analysis_id}/check_blocking"
|
|
117
|
-
)
|
|
118
|
-
self._set_blocked(process_response, blocked)
|
|
119
89
|
return process_response
|
|
120
90
|
|
|
121
91
|
async def output(
|
|
@@ -129,22 +99,15 @@ class AsyncHivetraceSDK(BaseHivetraceSDK):
|
|
|
129
99
|
application_id, message, additional_parameters
|
|
130
100
|
)
|
|
131
101
|
process_response = await self._send_request("/process_response/", payload)
|
|
132
|
-
|
|
102
|
+
|
|
133
103
|
analysis_id = self._extract_analysis_id(process_response)
|
|
134
104
|
if analysis_id:
|
|
135
105
|
if files:
|
|
136
106
|
files_response = await self._send_files(
|
|
137
107
|
f"/llm_response_analysis/{analysis_id}/attach_files", files
|
|
138
108
|
)
|
|
139
|
-
if
|
|
109
|
+
if self._is_failure_response(files_response):
|
|
140
110
|
return files_response
|
|
141
|
-
elif isinstance(files_response, dict) and not files_response.get("success", True):
|
|
142
|
-
return files_response
|
|
143
|
-
|
|
144
|
-
blocked = await self._get_blocking_status(
|
|
145
|
-
f"/llm_response_analysis/{analysis_id}/check_blocking"
|
|
146
|
-
)
|
|
147
|
-
self._set_blocked(process_response, blocked)
|
|
148
111
|
return process_response
|
|
149
112
|
|
|
150
113
|
async def function_call(
|
hivetrace/client/base.py
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import uuid
|
|
3
2
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
3
|
+
from typing import Any, Awaitable, Dict, List, Optional, Tuple, Union
|
|
5
4
|
|
|
6
5
|
import httpx
|
|
7
6
|
from pydantic import ValidationError
|
|
8
7
|
|
|
9
8
|
from ..errors import InvalidParameterError, MissingConfigError
|
|
10
|
-
from ..handlers import ErrorHandler
|
|
11
9
|
from ..models import (
|
|
12
10
|
FunctionCallRequest,
|
|
13
11
|
HivetraceResponse,
|
|
@@ -54,34 +52,6 @@ class BaseHivetraceSDK(ABC):
|
|
|
54
52
|
|
|
55
53
|
return value
|
|
56
54
|
|
|
57
|
-
@staticmethod
|
|
58
|
-
def _validate_application_id(application_id: str) -> str:
|
|
59
|
-
try:
|
|
60
|
-
return str(uuid.UUID(application_id))
|
|
61
|
-
except ValueError as e:
|
|
62
|
-
raise InvalidParameterError(
|
|
63
|
-
parameter="application_id", message="Invalid application_id format"
|
|
64
|
-
) from e
|
|
65
|
-
|
|
66
|
-
@staticmethod
|
|
67
|
-
def _validate_message(message: str) -> None:
|
|
68
|
-
if not isinstance(message, str) or not message.strip():
|
|
69
|
-
raise InvalidParameterError(
|
|
70
|
-
parameter="message", message="Message must be non-empty"
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
@staticmethod
|
|
74
|
-
def _validate_additional_parameters(
|
|
75
|
-
additional_parameters: Optional[Dict[str, Any]],
|
|
76
|
-
) -> None:
|
|
77
|
-
if additional_parameters is not None and not isinstance(
|
|
78
|
-
additional_parameters, dict
|
|
79
|
-
):
|
|
80
|
-
raise InvalidParameterError(
|
|
81
|
-
parameter="additional_parameters",
|
|
82
|
-
message="Additional parameters must be a dict or None",
|
|
83
|
-
)
|
|
84
|
-
|
|
85
55
|
def _build_request_args(
|
|
86
56
|
self, endpoint: str, payload: Dict[str, Any]
|
|
87
57
|
) -> Dict[str, Any]:
|
|
@@ -137,21 +107,29 @@ class BaseHivetraceSDK(ABC):
|
|
|
137
107
|
return None
|
|
138
108
|
|
|
139
109
|
@staticmethod
|
|
140
|
-
def
|
|
141
|
-
"""Sets 'blocked' flag on response when possible."""
|
|
110
|
+
def _is_failure_response(response: Any) -> bool:
|
|
142
111
|
try:
|
|
143
112
|
if isinstance(response, dict):
|
|
144
|
-
response
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
113
|
+
if response.get("success") is False:
|
|
114
|
+
return True
|
|
115
|
+
status = response.get("status")
|
|
116
|
+
if isinstance(status, str) and "success" not in status.lower():
|
|
117
|
+
return True
|
|
118
|
+
if response.get("error") or response.get("error_type"):
|
|
119
|
+
return True
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
if hasattr(response, "success") and getattr(response, "success") is False:
|
|
123
|
+
return True
|
|
124
|
+
status = getattr(response, "status", None)
|
|
125
|
+
if isinstance(status, str) and "success" not in status.lower():
|
|
126
|
+
return True
|
|
127
|
+
if getattr(response, "error", None) or getattr(response, "error_type", None):
|
|
128
|
+
return True
|
|
149
129
|
except Exception:
|
|
150
|
-
return
|
|
151
|
-
return response
|
|
130
|
+
return False
|
|
152
131
|
|
|
153
|
-
|
|
154
|
-
return ErrorHandler.handle_http_error(error)
|
|
132
|
+
return False
|
|
155
133
|
|
|
156
134
|
def _build_message_payload(
|
|
157
135
|
self,
|
|
@@ -166,7 +144,7 @@ class BaseHivetraceSDK(ABC):
|
|
|
166
144
|
message=message,
|
|
167
145
|
additional_parameters=additional_parameters,
|
|
168
146
|
)
|
|
169
|
-
return request_model.
|
|
147
|
+
return request_model.model_dump()
|
|
170
148
|
except ValidationError as e:
|
|
171
149
|
raise InvalidParameterError(
|
|
172
150
|
parameter="request_data", message=f"Validation failed: {e}"
|
|
@@ -191,7 +169,7 @@ class BaseHivetraceSDK(ABC):
|
|
|
191
169
|
func_result=func_result,
|
|
192
170
|
additional_parameters=additional_parameters,
|
|
193
171
|
)
|
|
194
|
-
return request_model.
|
|
172
|
+
return request_model.model_dump()
|
|
195
173
|
except ValidationError as e:
|
|
196
174
|
raise InvalidParameterError(
|
|
197
175
|
parameter="request_data", message=f"Validation failed: {e}"
|
|
@@ -203,7 +181,8 @@ class BaseHivetraceSDK(ABC):
|
|
|
203
181
|
application_id: str,
|
|
204
182
|
message: str,
|
|
205
183
|
additional_parameters: Optional[Dict[str, Any]] = None,
|
|
206
|
-
|
|
184
|
+
files: Optional[List[Tuple[str, bytes, str]]] = None,
|
|
185
|
+
) -> Union[HivetraceResponse, Awaitable[HivetraceResponse]]:
|
|
207
186
|
"""Sends user request to HiveTrace."""
|
|
208
187
|
pass
|
|
209
188
|
|
|
@@ -213,7 +192,8 @@ class BaseHivetraceSDK(ABC):
|
|
|
213
192
|
application_id: str,
|
|
214
193
|
message: str,
|
|
215
194
|
additional_parameters: Optional[Dict[str, Any]] = None,
|
|
216
|
-
|
|
195
|
+
files: Optional[List[Tuple[str, bytes, str]]] = None,
|
|
196
|
+
) -> Union[HivetraceResponse, Awaitable[HivetraceResponse]]:
|
|
217
197
|
"""Sends LLM response to HiveTrace."""
|
|
218
198
|
pass
|
|
219
199
|
|
|
@@ -226,11 +206,11 @@ class BaseHivetraceSDK(ABC):
|
|
|
226
206
|
func_args: str,
|
|
227
207
|
func_result: Optional[Union[Dict, str]] = None,
|
|
228
208
|
additional_parameters: Optional[Dict[str, Any]] = None,
|
|
229
|
-
) -> HivetraceResponse:
|
|
209
|
+
) -> Union[HivetraceResponse, Awaitable[HivetraceResponse]]:
|
|
230
210
|
"""Sends function call to HiveTrace."""
|
|
231
211
|
pass
|
|
232
212
|
|
|
233
213
|
@abstractmethod
|
|
234
|
-
def close(self) -> None:
|
|
214
|
+
def close(self) -> Union[None, Awaitable[None]]:
|
|
235
215
|
"""Closes HTTP session and frees resources."""
|
|
236
216
|
pass
|
hivetrace/client/sync_client.py
CHANGED
|
@@ -43,17 +43,15 @@ class SyncHivetraceSDK(BaseHivetraceSDK):
|
|
|
43
43
|
except Exception:
|
|
44
44
|
pass
|
|
45
45
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
def _post_and_parse(self, request_args: Dict[str, Any], endpoint: str) -> HivetraceResponse:
|
|
47
|
+
"""
|
|
48
|
+
Sends a POST request and parses the response.
|
|
49
|
+
"""
|
|
50
50
|
try:
|
|
51
51
|
response = self.session.post(**request_args)
|
|
52
52
|
response.raise_for_status()
|
|
53
|
-
|
|
54
53
|
api_data = response.json()
|
|
55
|
-
return ResponseBuilder.build_response_from_api(api_data)
|
|
56
|
-
|
|
54
|
+
return ResponseBuilder.build_response_from_api(api_data, endpoint=endpoint)
|
|
57
55
|
except httpx.HTTPStatusError as e:
|
|
58
56
|
return ErrorHandler.handle_http_error(e)
|
|
59
57
|
except httpx.ConnectError as e:
|
|
@@ -67,42 +65,17 @@ class SyncHivetraceSDK(BaseHivetraceSDK):
|
|
|
67
65
|
except Exception as e:
|
|
68
66
|
return ErrorHandler.handle_unexpected_error(e)
|
|
69
67
|
|
|
68
|
+
def _send_request(
|
|
69
|
+
self, endpoint: str, payload: Dict[str, Any]
|
|
70
|
+
) -> HivetraceResponse:
|
|
71
|
+
request_args = self._build_request_args(endpoint, payload)
|
|
72
|
+
return self._post_and_parse(request_args, endpoint)
|
|
73
|
+
|
|
70
74
|
def _send_files(
|
|
71
75
|
self, endpoint: str, files: List[Tuple[str, bytes, str]]
|
|
72
76
|
) -> HivetraceResponse:
|
|
73
77
|
request_args = self._build_files_request_args(endpoint, files)
|
|
74
|
-
|
|
75
|
-
response = self.session.post(**request_args)
|
|
76
|
-
response.raise_for_status()
|
|
77
|
-
|
|
78
|
-
api_data = response.json()
|
|
79
|
-
return ResponseBuilder.build_response_from_api(api_data)
|
|
80
|
-
|
|
81
|
-
except httpx.HTTPStatusError as e:
|
|
82
|
-
return ErrorHandler.handle_http_error(e)
|
|
83
|
-
except httpx.ConnectError as e:
|
|
84
|
-
return ErrorHandler.handle_connection_error(e)
|
|
85
|
-
except httpx.TimeoutException as e:
|
|
86
|
-
return ErrorHandler.handle_timeout_error(e)
|
|
87
|
-
except httpx.RequestError as e:
|
|
88
|
-
return ErrorHandler.handle_request_error(e)
|
|
89
|
-
except ValueError as e:
|
|
90
|
-
return ErrorHandler.handle_json_decode_error(e)
|
|
91
|
-
except Exception as e:
|
|
92
|
-
return ErrorHandler.handle_unexpected_error(e)
|
|
93
|
-
|
|
94
|
-
def _get_blocking_status(self, endpoint: str) -> Optional[bool]:
|
|
95
|
-
url = f"{self.hivetrace_url}/{endpoint.lstrip('/')}"
|
|
96
|
-
headers = {"Authorization": f"Bearer {self.hivetrace_access_token}"}
|
|
97
|
-
try:
|
|
98
|
-
response = self.session.get(
|
|
99
|
-
url, headers=headers, timeout=self._DEFAULT_TIMEOUT
|
|
100
|
-
)
|
|
101
|
-
response.raise_for_status()
|
|
102
|
-
data = response.json()
|
|
103
|
-
return data.get("blocked")
|
|
104
|
-
except Exception:
|
|
105
|
-
return None
|
|
78
|
+
return self._post_and_parse(request_args, endpoint)
|
|
106
79
|
|
|
107
80
|
def input(
|
|
108
81
|
self,
|
|
@@ -115,22 +88,15 @@ class SyncHivetraceSDK(BaseHivetraceSDK):
|
|
|
115
88
|
application_id, message, additional_parameters
|
|
116
89
|
)
|
|
117
90
|
process_response = self._send_request("/process_request/", payload)
|
|
118
|
-
|
|
91
|
+
|
|
119
92
|
analysis_id = self._extract_analysis_id(process_response)
|
|
120
93
|
if analysis_id:
|
|
121
94
|
if files:
|
|
122
95
|
files_response = self._send_files(
|
|
123
96
|
f"/user_prompt_analysis/{analysis_id}/attach_files", files
|
|
124
97
|
)
|
|
125
|
-
if
|
|
126
|
-
return files_response
|
|
127
|
-
elif isinstance(files_response, dict) and not files_response.get("success", True):
|
|
98
|
+
if self._is_failure_response(files_response):
|
|
128
99
|
return files_response
|
|
129
|
-
|
|
130
|
-
blocked = self._get_blocking_status(
|
|
131
|
-
f"/user_prompt_analysis/{analysis_id}/check_blocking"
|
|
132
|
-
)
|
|
133
|
-
self._set_blocked(process_response, blocked)
|
|
134
100
|
return process_response
|
|
135
101
|
|
|
136
102
|
def output(
|
|
@@ -144,22 +110,15 @@ class SyncHivetraceSDK(BaseHivetraceSDK):
|
|
|
144
110
|
application_id, message, additional_parameters
|
|
145
111
|
)
|
|
146
112
|
process_response = self._send_request("/process_response/", payload)
|
|
147
|
-
|
|
113
|
+
|
|
148
114
|
analysis_id = self._extract_analysis_id(process_response)
|
|
149
115
|
if analysis_id:
|
|
150
116
|
if files:
|
|
151
117
|
files_response = self._send_files(
|
|
152
118
|
f"/llm_response_analysis/{analysis_id}/attach_files", files
|
|
153
119
|
)
|
|
154
|
-
if
|
|
155
|
-
return files_response
|
|
156
|
-
elif isinstance(files_response, dict) and not files_response.get("success", True):
|
|
120
|
+
if self._is_failure_response(files_response):
|
|
157
121
|
return files_response
|
|
158
|
-
|
|
159
|
-
blocked = self._get_blocking_status(
|
|
160
|
-
f"/llm_response_analysis/{analysis_id}/check_blocking"
|
|
161
|
-
)
|
|
162
|
-
self._set_blocked(process_response, blocked)
|
|
163
122
|
return process_response
|
|
164
123
|
|
|
165
124
|
def function_call(
|
hivetrace/errors/api.py
CHANGED
|
@@ -8,7 +8,7 @@ from .base import HiveTraceError
|
|
|
8
8
|
class APIError(HiveTraceError):
|
|
9
9
|
"""Базовое исключение для API ошибок."""
|
|
10
10
|
|
|
11
|
-
def __init__(self, message: str, status_code: int = None):
|
|
11
|
+
def __init__(self, message: str, status_code: int | None = None):
|
|
12
12
|
super().__init__(message)
|
|
13
13
|
self.status_code = status_code
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@ class APIError(HiveTraceError):
|
|
|
16
16
|
class HTTPError(APIError):
|
|
17
17
|
"""Исключение для HTTP ошибок."""
|
|
18
18
|
|
|
19
|
-
def __init__(self, status_code: int, message: str = None):
|
|
19
|
+
def __init__(self, status_code: int, message: str | None = None):
|
|
20
20
|
message = message or f"HTTP error {status_code}"
|
|
21
21
|
super().__init__(message, status_code)
|
|
22
22
|
|