hivetrace 1.3.15__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 -67
- hivetrace/models/requests.py +21 -28
- hivetrace/models/responses.py +26 -21
- hivetrace/utils/error_helpers.py +25 -3
- {hivetrace-1.3.15.dist-info → hivetrace-1.4.0.dist-info}/METADATA +188 -203
- {hivetrace-1.3.15.dist-info → hivetrace-1.4.0.dist-info}/RECORD +21 -21
- {hivetrace-1.3.15.dist-info → hivetrace-1.4.0.dist-info}/WHEEL +0 -0
- {hivetrace-1.3.15.dist-info → hivetrace-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {hivetrace-1.3.15.dist-info → hivetrace-1.4.0.dist-info}/top_level.txt +0 -0
hivetrace/errors/validation.py
CHANGED
|
@@ -14,7 +14,7 @@ class ValidationError(HiveTraceError):
|
|
|
14
14
|
class InvalidParameterError(ValidationError):
|
|
15
15
|
"""Exception for invalid parameter."""
|
|
16
16
|
|
|
17
|
-
def __init__(self, parameter: str, message: str = None):
|
|
17
|
+
def __init__(self, parameter: str, message: str | None = None):
|
|
18
18
|
message = message or f"Invalid parameter: {parameter}"
|
|
19
19
|
super().__init__(message)
|
|
20
20
|
self.parameter = parameter
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
from typing import Any, Dict, Optional
|
|
3
2
|
|
|
4
|
-
from ..models.responses import HivetraceResponse, ProcessResponse
|
|
3
|
+
from ..models.responses import HivetraceResponse, ProcessResponse
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
class ResponseBuilder:
|
|
@@ -9,81 +8,29 @@ class ResponseBuilder:
|
|
|
9
8
|
Response builder for creating structured responses.
|
|
10
9
|
"""
|
|
11
10
|
|
|
12
|
-
@staticmethod
|
|
13
|
-
def build_success_response(
|
|
14
|
-
data: Dict[str, Any] = None, request_id: Optional[str] = None
|
|
15
|
-
) -> SuccessResponse:
|
|
16
|
-
"""Builds a successful response."""
|
|
17
|
-
return SuccessResponse(
|
|
18
|
-
success=True,
|
|
19
|
-
timestamp=datetime.utcnow().isoformat(),
|
|
20
|
-
request_id=request_id,
|
|
21
|
-
**(data or {}),
|
|
22
|
-
)
|
|
23
|
-
|
|
24
11
|
@staticmethod
|
|
25
12
|
def build_process_response(
|
|
26
|
-
|
|
27
|
-
trace_id: Optional[str] = None,
|
|
28
|
-
request_id: Optional[str] = None,
|
|
29
|
-
additional_data: Dict[str, Any] = None,
|
|
13
|
+
data: Optional[Dict[str, Any]] = None,
|
|
30
14
|
) -> ProcessResponse:
|
|
31
|
-
"""Builds a response
|
|
32
|
-
return ProcessResponse(
|
|
33
|
-
success=True,
|
|
34
|
-
timestamp=datetime.utcnow().isoformat(),
|
|
35
|
-
message_id=message_id,
|
|
36
|
-
trace_id=trace_id,
|
|
37
|
-
request_id=request_id,
|
|
38
|
-
**(additional_data or {}),
|
|
39
|
-
)
|
|
15
|
+
"""Builds a process response directly from API payload."""
|
|
16
|
+
return ProcessResponse(**(data or {}))
|
|
40
17
|
|
|
41
18
|
@staticmethod
|
|
42
19
|
def build_response_from_api(
|
|
43
|
-
api_response: Dict[str, Any],
|
|
20
|
+
api_response: Dict[str, Any],
|
|
21
|
+
request_id: Optional[str] = None,
|
|
22
|
+
*,
|
|
23
|
+
endpoint: Optional[str] = None,
|
|
44
24
|
) -> HivetraceResponse:
|
|
45
25
|
"""
|
|
46
|
-
Builds a response from API data.
|
|
26
|
+
Builds a typed response from API data when appropriate.
|
|
47
27
|
|
|
48
|
-
|
|
28
|
+
We keep the SDK "transparent" by only parsing into `ProcessResponse` for
|
|
29
|
+
`/process_*` endpoints. For other endpoints we return the raw dict, since
|
|
30
|
+
their payload shapes can vary.
|
|
49
31
|
"""
|
|
50
|
-
excluded_keys = {
|
|
51
|
-
"message_id",
|
|
52
|
-
"trace_id",
|
|
53
|
-
"request_id",
|
|
54
|
-
"success",
|
|
55
|
-
"timestamp",
|
|
56
|
-
"blocked",
|
|
57
|
-
}
|
|
58
32
|
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
or "message_id" in api_response
|
|
62
|
-
or "trace_id" in api_response
|
|
63
|
-
):
|
|
64
|
-
return ResponseBuilder.build_process_response(
|
|
65
|
-
message_id=api_response.get("message_id"),
|
|
66
|
-
trace_id=api_response.get("trace_id"),
|
|
67
|
-
request_id=api_response.get("request_id", request_id),
|
|
68
|
-
additional_data={
|
|
69
|
-
k: v
|
|
70
|
-
for k, v in api_response.items()
|
|
71
|
-
if k not in excluded_keys
|
|
72
|
-
},
|
|
73
|
-
)
|
|
33
|
+
if endpoint and endpoint.startswith("/process_"):
|
|
34
|
+
return ResponseBuilder.build_process_response(api_response)
|
|
74
35
|
|
|
75
36
|
return api_response
|
|
76
|
-
|
|
77
|
-
@staticmethod
|
|
78
|
-
def add_request_id(
|
|
79
|
-
response: HivetraceResponse, request_id: str
|
|
80
|
-
) -> HivetraceResponse:
|
|
81
|
-
"""Adds request_id to an existing response."""
|
|
82
|
-
if isinstance(response, dict):
|
|
83
|
-
response["request_id"] = request_id
|
|
84
|
-
return response
|
|
85
|
-
elif hasattr(response, "request_id"):
|
|
86
|
-
response.request_id = request_id
|
|
87
|
-
return response
|
|
88
|
-
else:
|
|
89
|
-
return response
|
hivetrace/models/requests.py
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
from typing import Any, Dict, Optional, Union
|
|
2
2
|
from uuid import UUID
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel, Field,
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class BaseRequest(BaseModel):
|
|
8
8
|
"""Base request model."""
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
model_config = ConfigDict(extra="forbid", use_enum_values=True)
|
|
11
|
+
|
|
12
|
+
@field_validator("application_id", check_fields=False)
|
|
13
|
+
@classmethod
|
|
14
|
+
def validate_application_id(cls, v: str) -> str:
|
|
15
|
+
"""Validation of UUID."""
|
|
16
|
+
try:
|
|
17
|
+
UUID(v)
|
|
18
|
+
return v
|
|
19
|
+
except ValueError:
|
|
20
|
+
raise ValueError("application_id must be a valid UUID")
|
|
13
21
|
|
|
14
22
|
|
|
15
23
|
class MessageRequest(BaseRequest):
|
|
@@ -21,17 +29,9 @@ class MessageRequest(BaseRequest):
|
|
|
21
29
|
None, description="Additional parameters"
|
|
22
30
|
)
|
|
23
31
|
|
|
24
|
-
@
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
try:
|
|
28
|
-
UUID(v)
|
|
29
|
-
return v
|
|
30
|
-
except ValueError:
|
|
31
|
-
raise ValueError("application_id must be a valid UUID")
|
|
32
|
-
|
|
33
|
-
@validator("message")
|
|
34
|
-
def validate_message(cls, v):
|
|
32
|
+
@field_validator("message")
|
|
33
|
+
@classmethod
|
|
34
|
+
def validate_message(cls, v: str) -> str:
|
|
35
35
|
"""Validation of message."""
|
|
36
36
|
if not v.strip():
|
|
37
37
|
raise ValueError("Message cannot be empty")
|
|
@@ -64,24 +64,17 @@ class FunctionCallRequest(BaseRequest):
|
|
|
64
64
|
None, description="Additional parameters"
|
|
65
65
|
)
|
|
66
66
|
|
|
67
|
-
@
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
try:
|
|
71
|
-
UUID(v)
|
|
72
|
-
return v
|
|
73
|
-
except ValueError:
|
|
74
|
-
raise ValueError("application_id must be a valid UUID")
|
|
75
|
-
|
|
76
|
-
@validator("func_name")
|
|
77
|
-
def validate_func_name(cls, v):
|
|
67
|
+
@field_validator("func_name")
|
|
68
|
+
@classmethod
|
|
69
|
+
def validate_func_name(cls, v: str) -> str:
|
|
78
70
|
"""Validation of function name."""
|
|
79
71
|
if not v.strip():
|
|
80
72
|
raise ValueError("Function name cannot be empty")
|
|
81
73
|
return v.strip()
|
|
82
74
|
|
|
83
|
-
@
|
|
84
|
-
|
|
75
|
+
@field_validator("tool_call_id")
|
|
76
|
+
@classmethod
|
|
77
|
+
def validate_tool_call_id(cls, v: str) -> str:
|
|
85
78
|
"""Validation of tool call ID."""
|
|
86
79
|
if not v.strip():
|
|
87
80
|
raise ValueError("Tool call ID cannot be empty")
|
hivetrace/models/responses.py
CHANGED
|
@@ -1,44 +1,49 @@
|
|
|
1
1
|
from typing import Any, Dict, List, Literal, Optional, Union
|
|
2
2
|
|
|
3
|
-
from pydantic import BaseModel, Field
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class BaseResponse(BaseModel):
|
|
7
7
|
"""Base response model."""
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
extra = "allow"
|
|
11
|
-
use_enum_values = True
|
|
9
|
+
model_config = ConfigDict(extra="allow", use_enum_values=True)
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
class SuccessResponse(BaseResponse):
|
|
15
13
|
"""Success response from HiveTrace API."""
|
|
16
14
|
|
|
17
|
-
success: bool = Field(True, description="Success flag")
|
|
18
|
-
timestamp: Optional[str] = Field(None, description="Timestamp")
|
|
19
|
-
request_id: Optional[str] = Field(None, description="Request ID for tracking")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class ProcessResponse(
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
15
|
+
success: bool = Field(default=True, description="Success flag")
|
|
16
|
+
timestamp: Optional[str] = Field(default=None, description="Timestamp")
|
|
17
|
+
request_id: Optional[str] = Field(default=None, description="Request ID for tracking")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ProcessResponse(BaseResponse):
|
|
21
|
+
"""
|
|
22
|
+
Response model for `/process_request/` and `/process_response/`.
|
|
23
|
+
Matches the API payload shape (no SDK-added fields).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
request_id: Optional[str] = Field(default=None, description="Request ID for tracking")
|
|
27
|
+
schema_version: Optional[str] = Field(default=None, description="Schema version")
|
|
28
|
+
status: Optional[str] = Field(default=None, description="Status string")
|
|
29
|
+
errors: List[Any] = Field(default_factory=list, description="Errors list")
|
|
30
|
+
tokens: Optional[Dict[str, Any]] = Field(default=None, description="Token usage info")
|
|
31
|
+
guardrails: Optional[Dict[str, Any]] = Field(default=None, description="Guardrails result")
|
|
32
|
+
custom_policy: Optional[Dict[str, Any]] = Field(
|
|
33
|
+
default=None, description="Custom policy result"
|
|
30
34
|
)
|
|
35
|
+
dataclean: Optional[Dict[str, Any]] = Field(default=None, description="Data cleaning result")
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
class ErrorResponse(BaseResponse):
|
|
34
39
|
"""Base response model with error."""
|
|
35
40
|
|
|
36
|
-
success: bool = Field(False, description="Success flag")
|
|
41
|
+
success: bool = Field(default=False, description="Success flag")
|
|
37
42
|
error: str = Field(..., description="Error message")
|
|
38
43
|
error_type: str = Field(..., description="Error type")
|
|
39
44
|
details: str = Field(..., description="Error details")
|
|
40
|
-
status_code: Optional[int] = Field(None, description="HTTP status code")
|
|
41
|
-
request_id: Optional[str] = Field(None, description="Request ID for tracking")
|
|
45
|
+
status_code: Optional[int] = Field(default=None, description="HTTP status code")
|
|
46
|
+
request_id: Optional[str] = Field(default=None, description="Request ID for tracking")
|
|
42
47
|
|
|
43
48
|
|
|
44
49
|
class ConnectionErrorResponse(ErrorResponse):
|
|
@@ -81,7 +86,7 @@ class ValidationErrorResponse(ErrorResponse):
|
|
|
81
86
|
|
|
82
87
|
error_type: Literal["validation_error"] = "validation_error"
|
|
83
88
|
field_errors: Optional[List[Dict[str, Any]]] = Field(
|
|
84
|
-
None, description="Field validation errors"
|
|
89
|
+
default=None, description="Field validation errors"
|
|
85
90
|
)
|
|
86
91
|
|
|
87
92
|
|
hivetrace/utils/error_helpers.py
CHANGED
|
@@ -10,8 +10,19 @@ from ..models.responses import HivetraceResponse
|
|
|
10
10
|
def is_error_response(response: HivetraceResponse) -> bool:
|
|
11
11
|
"""Checks if the response is an error."""
|
|
12
12
|
if isinstance(response, dict):
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
if response.get("error") or response.get("error_type"):
|
|
14
|
+
return True
|
|
15
|
+
status = response.get("status")
|
|
16
|
+
if isinstance(status, str) and "success" not in status.lower():
|
|
17
|
+
return True
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
if getattr(response, "error", None) or getattr(response, "error_type", None):
|
|
21
|
+
return True
|
|
22
|
+
status = getattr(response, "status", None)
|
|
23
|
+
if isinstance(status, str) and "success" not in status.lower():
|
|
24
|
+
return True
|
|
25
|
+
return False
|
|
15
26
|
|
|
16
27
|
|
|
17
28
|
def get_error_type(response: HivetraceResponse) -> Optional[str]:
|
|
@@ -74,5 +85,16 @@ def get_status_code(response: HivetraceResponse) -> Optional[int]:
|
|
|
74
85
|
def is_success_response(response: HivetraceResponse) -> bool:
|
|
75
86
|
"""Checks if the response is a success."""
|
|
76
87
|
if isinstance(response, dict):
|
|
88
|
+
status = response.get("status")
|
|
89
|
+
if isinstance(status, str):
|
|
90
|
+
return "success" in status.lower() and "error" not in response
|
|
77
91
|
return response.get("success", True) and "error" not in response
|
|
78
|
-
|
|
92
|
+
|
|
93
|
+
if hasattr(response, "success"):
|
|
94
|
+
return bool(getattr(response, "success", True))
|
|
95
|
+
|
|
96
|
+
status = getattr(response, "status", None)
|
|
97
|
+
if isinstance(status, str):
|
|
98
|
+
return "success" in status.lower()
|
|
99
|
+
|
|
100
|
+
return False
|