-
Notifications
You must be signed in to change notification settings - Fork 692
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sdk): client, annotations (#2452)
- Loading branch information
1 parent
36dd9b3
commit 8b0ad84
Showing
11 changed files
with
399 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import pytest | ||
from traceloop.sdk.client import Client | ||
from traceloop.sdk.client.http import HTTPClient | ||
from traceloop.sdk.annotation.user_feedback import UserFeedback | ||
|
||
|
||
def test_client_initialization(): | ||
"""Test basic client initialization""" | ||
client = Client(api_key="test-key", app_name="test-app") | ||
|
||
assert client.app_name == "test-app" | ||
assert client.api_key == "test-key" | ||
assert client.api_endpoint == "https://api.traceloop.com" | ||
assert isinstance(client._http, HTTPClient) | ||
|
||
|
||
def test_client_custom_endpoint(): | ||
"""Test client initialization with custom endpoint""" | ||
client = Client(api_key="test-key", app_name="test-app", api_endpoint="https://custom.endpoint.com") | ||
|
||
assert client.api_endpoint == "https://custom.endpoint.com" | ||
assert client._http.base_url == "https://custom.endpoint.com" | ||
|
||
|
||
def test_client_default_app_name(): | ||
"""Test client initialization with default app_name""" | ||
client = Client(api_key="test-key") | ||
|
||
# Default app_name should be sys.argv[0] | ||
import sys | ||
|
||
assert client.app_name == sys.argv[0] | ||
|
||
|
||
@pytest.mark.parametrize("api_key", [None, "", " "]) | ||
def test_client_requires_api_key(api_key): | ||
"""Test that client requires a valid API key""" | ||
with pytest.raises(ValueError, match="API key is required"): | ||
Client(api_key=api_key) | ||
|
||
|
||
def test_user_feedback_initialization(): | ||
"""Test user_feedback is properly initialized""" | ||
client = Client(api_key="test-key", app_name="test-app") | ||
|
||
assert isinstance(client.user_feedback, UserFeedback) | ||
assert client.user_feedback._http == client._http | ||
assert client.user_feedback._app_name == client.app_name |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
""" | ||
Tests for the UserFeedback class. | ||
These tests verify: | ||
1. Proper initialization of UserFeedback instances | ||
2. Basic feedback submission with minimal parameters | ||
3. Handling of complex tag structures | ||
4. Proper API endpoint construction and payload formatting | ||
""" | ||
|
||
import pytest | ||
from unittest.mock import Mock | ||
from traceloop.sdk.annotation.user_feedback import UserFeedback | ||
from traceloop.sdk.client.http import HTTPClient | ||
|
||
|
||
@pytest.fixture | ||
def mock_http(): | ||
"""Create a mock HTTP client""" | ||
http = Mock(spec=HTTPClient) | ||
http.post.return_value = {"status": "success"} | ||
return http | ||
|
||
|
||
@pytest.fixture | ||
def user_feedback(mock_http): | ||
"""Create a UserFeedback instance with mock HTTP client""" | ||
return UserFeedback(mock_http, "test-app") | ||
|
||
|
||
def test_user_feedback_initialization(mock_http): | ||
"""Test UserFeedback is properly initialized""" | ||
feedback = UserFeedback(mock_http, "test-app") | ||
assert feedback._http == mock_http | ||
assert feedback._app_name == "test-app" | ||
|
||
|
||
def test_create_basic_feedback(user_feedback, mock_http): | ||
"""Test creating basic user feedback""" | ||
user_feedback.create( | ||
annotation_task="task_123", entity_id="instance_456", tags={"sentiment": "positive"} | ||
) | ||
|
||
mock_http.post.assert_called_once_with( | ||
"annotation-tasks/task_123/annotations", | ||
{ | ||
"entity_instance_id": "instance_456", | ||
"tags": {"sentiment": "positive"}, | ||
"source": "sdk", | ||
"flow": "user_feedback", | ||
"actor": { | ||
"type": "service", | ||
"id": "test-app", | ||
}, | ||
}, | ||
) | ||
|
||
|
||
def test_create_feedback_complex_tags(user_feedback, mock_http): | ||
"""Test creating user feedback with complex tags""" | ||
tags = {"sentiment": "positive", "relevance": 0.95, "tones": ["happy", "nice"]} | ||
|
||
user_feedback.create(annotation_task="task_123", entity_id="instance_456", tags=tags) | ||
|
||
mock_http.post.assert_called_once_with( | ||
"annotation-tasks/task_123/annotations", | ||
{ | ||
"entity_instance_id": "instance_456", | ||
"tags": tags, | ||
"source": "sdk", | ||
"flow": "user_feedback", | ||
"actor": { | ||
"type": "service", | ||
"id": "test-app", | ||
}, | ||
}, | ||
) | ||
|
||
|
||
def test_create_feedback_parameter_validation(user_feedback): | ||
"""Test parameter validation for feedback creation""" | ||
with pytest.raises(ValueError, match="annotation_task is required"): | ||
user_feedback.create(annotation_task="", entity_id="instance_456", tags={"sentiment": "positive"}) | ||
|
||
with pytest.raises(ValueError, match="entity_id is required"): | ||
user_feedback.create(annotation_task="task_123", entity_id="", tags={"sentiment": "positive"}) | ||
|
||
with pytest.raises(ValueError, match="tags cannot be empty"): | ||
user_feedback.create(annotation_task="task_123", entity_id="instance_456", tags={}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from .base_annotation import BaseAnnotation | ||
from .user_feedback import UserFeedback | ||
|
||
__all__ = ["BaseAnnotation", "UserFeedback"] |
71 changes: 71 additions & 0 deletions
71
packages/traceloop-sdk/traceloop/sdk/annotation/base_annotation.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
from typing import Dict, Any | ||
|
||
from ..client.http import HTTPClient | ||
|
||
|
||
class BaseAnnotation: | ||
""" | ||
Annotation class for creating annotations in Traceloop. | ||
This class provides functionality to create annotations for specific tasks. | ||
""" | ||
|
||
_http: HTTPClient | ||
_app_name: str | ||
|
||
def __init__(self, http: HTTPClient, app_name: str, flow: str): | ||
self._http = http | ||
self._app_name = app_name | ||
self._flow = flow | ||
|
||
def create( | ||
self, | ||
annotation_task: str, | ||
entity_id: str, | ||
tags: Dict[str, Any], | ||
) -> None: | ||
"""Create an user feedback annotation for a specific task. | ||
Args: | ||
annotation_task (str): The ID/slug of the annotation task to report to. | ||
Can be found at app.traceloop.com/annotation_tasks/:annotation_task_id | ||
entity_id (str): The ID of the specific entity instance being annotated, should be reported | ||
in the association properties | ||
tags (Dict[str, Any]): Dictionary containing the tags to be reported. | ||
Should match the tags defined in the annotation task | ||
Example: | ||
```python | ||
client = Client(api_key="your-key") | ||
client.annotation.create( | ||
annotation_task="task_123", | ||
entity_id="instance_456", | ||
tags={ | ||
"sentiment": "positive", | ||
"relevance": 0.95, | ||
"tones": ["happy", "nice"] | ||
}, | ||
) | ||
``` | ||
""" | ||
|
||
if not annotation_task: | ||
raise ValueError("annotation_task is required") | ||
if not entity_id: | ||
raise ValueError("entity_id is required") | ||
if not tags: | ||
raise ValueError("tags cannot be empty") | ||
|
||
self._http.post( | ||
f"annotation-tasks/{annotation_task}/annotations", | ||
{ | ||
"entity_instance_id": entity_id, | ||
"tags": tags, | ||
"source": "sdk", | ||
"flow": self._flow, | ||
"actor": { | ||
"type": "service", | ||
"id": self._app_name, | ||
}, | ||
}, | ||
) |
43 changes: 43 additions & 0 deletions
43
packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
from typing import Any, Dict | ||
|
||
from traceloop.sdk.client.http import HTTPClient | ||
from .base_annotation import BaseAnnotation | ||
|
||
|
||
class UserFeedback(BaseAnnotation): | ||
def __init__(self, http: HTTPClient, app_name: str): | ||
super().__init__(http, app_name, "user_feedback") | ||
|
||
|
||
def create( | ||
self, | ||
annotation_task: str, | ||
entity_instance_id: str, | ||
tags: Dict[str, Any], | ||
) -> None: | ||
"""Create an annotation for a specific task. | ||
Args: | ||
annotation_task (str): The ID/slug of the annotation task to report to. | ||
Can be found at app.traceloop.com/annotation_tasks/:annotation_task_id | ||
entity_instance_id (str): The ID of the specific entity instance being annotated, should be reported | ||
in the association properties | ||
tags (Dict[str, Any]): Dictionary containing the tags to be reported. | ||
Should match the tags defined in the annotation task | ||
Example: | ||
```python | ||
client = Client(api_key="your-key") | ||
client.annotation.create( | ||
annotation_task="task_123", | ||
entity_instance_id="instance_456", | ||
tags={ | ||
"sentiment": "positive", | ||
"relevance": 0.95, | ||
"tones": ["happy", "nice"] | ||
}, | ||
) | ||
``` | ||
""" | ||
|
||
return BaseAnnotation.create(self, annotation_task, entity_instance_id, tags) |
Oops, something went wrong.