Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 1 addition & 32 deletions src/sentry/seer/endpoints/organization_seer_explorer_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@

import logging

import sentry_sdk
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied
from rest_framework.request import Request
from rest_framework.response import Response

from sentry import features
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission
from sentry.conduit.auth import get_conduit_credentials
from sentry.models.organization import Organization
from sentry.ratelimits.config import RateLimitConfig
from sentry.seer.explorer.client import SeerExplorerClient
Expand Down Expand Up @@ -110,7 +107,6 @@ def post(

Returns:
- run_id: The run ID.
- conduit: Optional Conduit credentials for streaming (if streaming is enabled).
"""
has_access, error = has_seer_explorer_access_with_detail(organization, request.user)
if not has_access:
Expand All @@ -125,14 +121,6 @@ def post(
insert_index = validated_data.get("insert_index")
on_page_context = validated_data.get("on_page_context")

# Generate Conduit credentials for streaming if enabled
conduit_credentials = None
if features.has("organizations:seer-explorer-streaming", organization):
try:
conduit_credentials = get_conduit_credentials(organization.id)
except ValueError as e:
sentry_sdk.capture_exception(e, level="warning")

try:
enable_coding = organization.get_option("sentry:enable_seer_coding", True)
client = SeerExplorerClient(
Expand All @@ -148,33 +136,14 @@ def post(
prompt=query,
insert_index=insert_index,
on_page_context=on_page_context,
conduit_channel_id=(
conduit_credentials.channel_id if conduit_credentials else None
),
conduit_url=conduit_credentials.url if conduit_credentials else None,
)
else:
# Start new conversation
Comment thread
Mihir-Mavalankar marked this conversation as resolved.
result_run_id = client.start_run(
prompt=query,
on_page_context=on_page_context,
conduit_channel_id=(
conduit_credentials.channel_id if conduit_credentials else None
),
conduit_url=conduit_credentials.url if conduit_credentials else None,
)

# Build response
response_data: dict[str, object] = {"run_id": result_run_id}

# Include conduit credentials for frontend if streaming is enabled
if conduit_credentials:
response_data["conduit"] = {
"token": conduit_credentials.token,
"channel_id": conduit_credentials.channel_id,
"url": conduit_credentials.url,
}

return Response(response_data)
return Response({"run_id": result_run_id})
except SeerPermissionError as e:
raise PermissionDenied(e.message) from e
18 changes: 0 additions & 18 deletions src/sentry/seer/explorer/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,6 @@ def start_run(
artifact_key: str | None = None,
artifact_schema: type[BaseModel] | None = None,
metadata: dict[str, Any] | None = None,
conduit_channel_id: str | None = None,
conduit_url: str | None = None,
request: Request | None = None,
) -> int:
"""
Expand All @@ -235,8 +233,6 @@ def start_run(
artifact_key: Optional key to identify this artifact (required if artifact_schema is provided)
artifact_schema: Optional Pydantic model to generate a structured artifact
metadata: Optional metadata to store with the run (e.g., stopping_point, group_id)
conduit_channel_id: Optional Conduit channel ID for streaming
conduit_url: Optional Conduit URL for streaming
request: Optional rest_framework Request object from endpoints.

Returns:
Expand Down Expand Up @@ -293,11 +289,6 @@ def start_run(
if metadata:
payload["metadata"] = metadata

# Add conduit params for streaming if provided
if conduit_channel_id and conduit_url:
payload["conduit_channel_id"] = conduit_channel_id
payload["conduit_url"] = conduit_url

body = orjson.dumps(payload, option=orjson.OPT_NON_STR_KEYS)

response = make_signed_seer_api_request(
Expand All @@ -320,8 +311,6 @@ def continue_run(
on_page_context: str | None = None,
artifact_key: str | None = None,
artifact_schema: type[BaseModel] | None = None,
conduit_channel_id: str | None = None,
conduit_url: str | None = None,
) -> int:
"""
Continue an existing Seer Explorer session. This allows you to add follow-up queries to an ongoing conversation.
Expand All @@ -333,8 +322,6 @@ def continue_run(
on_page_context: Optional context from the user's screen
artifact_key: Optional key for a new artifact to generate in this step
artifact_schema: Optional Pydantic model for the new artifact (required if artifact_key is provided)
conduit_channel_id: Optional Conduit channel ID for streaming
conduit_url: Optional Conduit URL for streaming

Returns:
int: The run ID (same as input)
Expand Down Expand Up @@ -366,11 +353,6 @@ def continue_run(
payload["artifact_key"] = artifact_key
payload["artifact_schema"] = artifact_schema.schema()

# Add conduit params for streaming if provided
if conduit_channel_id and conduit_url:
payload["conduit_channel_id"] = conduit_channel_id
payload["conduit_url"] = conduit_url

body = orjson.dumps(payload, option=orjson.OPT_NON_STR_KEYS)

response = make_signed_seer_api_request(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
import base64
from typing import Any
from unittest.mock import ANY, MagicMock, patch

from django.test.utils import override_settings

Comment thread
Mihir-Mavalankar marked this conversation as resolved.
from sentry.models.organizationmember import OrganizationMember
from sentry.seer.explorer.client_utils import collect_user_org_context
from sentry.silo.safety import unguarded_write
from sentry.testutils.cases import APITestCase
from sentry.testutils.helpers.features import with_feature
from sentry.testutils.requests import make_request
from tests.sentry.utils.test_jwt import RS256_KEY

RS256_KEY_B64 = base64.b64encode(RS256_KEY.encode()).decode()
CONDUIT_SETTINGS = {
"CONDUIT_GATEWAY_PRIVATE_KEY": RS256_KEY_B64,
"CONDUIT_GATEWAY_JWT_ISSUER": "sentry",
"CONDUIT_GATEWAY_JWT_AUDIENCE": "conduit",
"CONDUIT_GATEWAY_URL": "https://conduit.example.com",
}


@with_feature("organizations:seer-explorer")
Expand Down Expand Up @@ -72,36 +60,6 @@ def test_post_with_empty_query_returns_400(self) -> None:

assert response.status_code == 400

@patch("sentry.seer.endpoints.organization_seer_explorer_chat.SeerExplorerClient")
def test_post_without_streaming_flag_no_conduit(self, mock_client_class: MagicMock):
mock_client_class.return_value.start_run.return_value = 123

response = self.client.post(self.url, {"query": "test"}, format="json")

assert response.status_code == 200
assert response.data == {"run_id": 123}
assert "conduit" not in response.data

@override_settings(**CONDUIT_SETTINGS)
@with_feature("organizations:seer-explorer-streaming")
@patch("sentry.seer.endpoints.organization_seer_explorer_chat.SeerExplorerClient")
def test_post_with_streaming_flag_includes_conduit(self, mock_client_class: MagicMock):
mock_client_class.return_value.start_run.return_value = 123

response = self.client.post(self.url, {"query": "test"}, format="json")

assert response.status_code == 200
assert response.data["run_id"] == 123
assert "conduit" in response.data
assert "token" in response.data["conduit"]
assert "channel_id" in response.data["conduit"]
assert "url" in response.data["conduit"]
# Verify conduit params passed to client
mock_client_class.return_value.start_run.assert_called_once()
call_kwargs = mock_client_class.return_value.start_run.call_args.kwargs
assert call_kwargs["conduit_channel_id"] is not None
assert call_kwargs["conduit_url"] is not None

@patch("sentry.seer.endpoints.organization_seer_explorer_chat.SeerExplorerClient")
def test_post_new_conversation_calls_client(self, mock_client_class: MagicMock):
mock_client = MagicMock()
Expand All @@ -121,8 +79,6 @@ def test_post_new_conversation_calls_client(self, mock_client_class: MagicMock):
mock_client.start_run.assert_called_once_with(
prompt="What is this error about?",
on_page_context=None,
conduit_channel_id=None,
conduit_url=None,
)

@patch("sentry.seer.endpoints.organization_seer_explorer_chat.SeerExplorerClient")
Expand All @@ -147,8 +103,6 @@ def test_post_new_conversation_with_coding_option(self, mock_client_class: Magic
mock_client.start_run.assert_called_once_with(
prompt="What is this error about?",
on_page_context=None,
conduit_channel_id=None,
conduit_url=None,
)

@patch("sentry.seer.endpoints.organization_seer_explorer_chat.SeerExplorerClient")
Expand All @@ -175,8 +129,6 @@ def test_post_continue_conversation_calls_client(self, mock_client_class: MagicM
prompt="Follow up question",
insert_index=2,
on_page_context=None,
conduit_channel_id=None,
conduit_url=None,
)

@patch("sentry.seer.endpoints.organization_seer_explorer_chat.SeerExplorerClient")
Expand Down Expand Up @@ -208,8 +160,6 @@ def test_post_continue_conversation_with_coding_option(
prompt="Follow up question",
insert_index=2,
on_page_context=None,
conduit_channel_id=None,
conduit_url=None,
)


Expand Down
Loading