Skip to content

Commit e544ac1

Browse files
authored
[ST-2214] Add more Information to Sentry Error + Solve absolute Url Bug
1 parent fb15f37 commit e544ac1

6 files changed

Lines changed: 107 additions & 4 deletions

File tree

apps/projects/utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
)
1515
from apps.summarization.export_utils.core import generate_full_export
1616
from apps.summarization.pydantic_models import ProjectSummaryResponse
17+
from apps.summarization.sentry_tags import set_sentry_project_tags
1718
from apps.summarization.services import AIService
1819

1920
logger = logging.getLogger(__name__)
@@ -36,6 +37,8 @@ def generate_project_summary(
3637
Raises:
3738
Exception: Re-raises any exception from the AI service (caller handles display/retry).
3839
"""
40+
set_sentry_project_tags(project)
41+
3942
export_data = generate_full_export(project)
4043

4144
if request is not None or base_url:
@@ -46,7 +49,8 @@ def generate_project_summary(
4649
try:
4750
service = AIService()
4851
document_response = service.request_vision_dict(
49-
documents_dict=documents_dict
52+
documents_dict=documents_dict,
53+
project=project,
5054
)
5155
integrate_document_summaries(
5256
export_data,

apps/summarization/export_utils/attachments/handlers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
def _make_absolute_url(attachment_url, request=None, base_url=None):
22
"""Build absolute URL from attachment_url using request or base_url."""
3+
if not attachment_url:
4+
return None
5+
6+
if attachment_url.startswith(("http://", "https://")):
7+
return attachment_url
8+
39
if request is not None:
410
return request.build_absolute_uri(attachment_url)
511
if base_url:

apps/summarization/providers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from .llm_json import parse_structured_llm_json
2020
from .pydantic_models import DocumentSummaryResponse
21+
from .sentry_tags import ensure_sentry_project_tags
2122

2223
logger = logging.getLogger(__name__)
2324

@@ -196,6 +197,7 @@ def text_request(
196197
)
197198

198199
try:
200+
ensure_sentry_project_tags()
199201
result = agent.run_sync(request.prompt())
200202
response = result.output
201203

@@ -301,6 +303,7 @@ def multimodal_request(
301303
user_content.append(ImageUrl(url=url))
302304

303305
try:
306+
ensure_sentry_project_tags()
304307
result = agent.run_sync(user_content)
305308
response = result.output
306309

apps/summarization/sentry_tags.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Sentry tags for project-scoped summarization calls."""
2+
3+
from __future__ import annotations
4+
5+
from contextvars import ContextVar
6+
7+
from sentry_sdk import set_tag
8+
9+
_current_project: ContextVar = ContextVar("sentry_project", default=None)
10+
11+
12+
def set_sentry_project_tags(project) -> None:
13+
"""Bind project tags for the current context and Sentry scope."""
14+
set_tag("project_id", project.id)
15+
set_tag("project_slug", project.slug)
16+
_current_project.set(project)
17+
18+
19+
def ensure_sentry_project_tags() -> None:
20+
"""Re-apply project tags before nested SDK calls (e.g. OpenAI client)."""
21+
project = _current_project.get()
22+
if project is not None:
23+
set_tag("project_id", project.id)
24+
set_tag("project_slug", project.slug)

apps/summarization/services.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .pydantic_models import DocumentSummaryResponse
1919
from .pydantic_models import ProjectSummaryResponse
2020
from .pydantic_models import SummaryItem
21+
from .sentry_tags import set_sentry_project_tags
2122
from .utils import extract_text_from_document
2223

2324
logger = logging.getLogger(__name__)
@@ -78,6 +79,7 @@ def project_summarize(
7879
- If allow_regeneration is False and a summary exists, always return the latest summary
7980
without generating a new one, even when the hash changed.
8081
"""
82+
set_sentry_project_tags(project)
8183
request = SummaryRequest(text=text, prompt=prompt)
8284
latest = self._get_latest_summary(project)
8385
text_hash = ProjectSummary.compute_hash(text)
@@ -185,16 +187,24 @@ def _save_to_cache(
185187
logger.info(f"Cached summary for project {project.id}")
186188

187189
def request_vision_dict(
188-
self, documents_dict: dict[str, str], prompt: str | None = None
190+
self,
191+
documents_dict: dict[str, str],
192+
prompt: str | None = None,
193+
project=None,
189194
) -> DocumentSummaryResponse:
190195
"""Process documents from dictionary format."""
191196
items = [DocumentInputItem(handle=h, url=u) for h, u in documents_dict.items()]
192-
return self.request_vision(items, prompt)
197+
return self.request_vision(items, prompt, project=project)
193198

194199
def request_vision(
195-
self, documents: list[DocumentInputItem], prompt: str | None = None
200+
self,
201+
documents: list[DocumentInputItem],
202+
prompt: str | None = None,
203+
project=None,
196204
) -> DocumentSummaryResponse:
197205
"""Process documents and images, return combined summaries."""
206+
if project is not None:
207+
set_sentry_project_tags(project)
198208
docs, images = self._split_documents(documents)
199209

200210
results = []
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Tests for document attachment URL handling in summarization exports."""
2+
3+
from apps.summarization.export_utils.attachments.handlers import _make_absolute_url
4+
from apps.summarization.export_utils.attachments.handlers import (
5+
collect_document_attachments,
6+
)
7+
8+
9+
def test_make_absolute_url_keeps_existing_absolute_url_with_base_url():
10+
media_url = (
11+
"https://roots-media-prod.liqd.net/uploads/Vanita/2025/08/04/"
12+
"Innovationswerkstatt%201_Ablaufplanung_Stand%2001-08-2025.pdf"
13+
)
14+
15+
assert (
16+
_make_absolute_url(
17+
media_url,
18+
base_url="https://beteiligung-roots.org",
19+
)
20+
== media_url
21+
)
22+
23+
24+
def test_make_absolute_url_prefixes_relative_path_with_base_url():
25+
assert (
26+
_make_absolute_url(
27+
"/uploads/example.pdf",
28+
base_url="https://beteiligung-roots.org",
29+
)
30+
== "https://beteiligung-roots.org/uploads/example.pdf"
31+
)
32+
33+
34+
def test_collect_document_attachments_keeps_absolute_media_urls():
35+
export_data = {
36+
"project": {
37+
"information_attachments": [
38+
"https://roots-media-prod.liqd.net/uploads/example.pdf",
39+
],
40+
"result_attachments": [],
41+
}
42+
}
43+
44+
documents_dict, handle_to_source = collect_document_attachments(
45+
export_data,
46+
base_url="https://beteiligung-roots.org",
47+
)
48+
49+
assert documents_dict == {
50+
"project_information_attachment_0": (
51+
"https://roots-media-prod.liqd.net/uploads/example.pdf"
52+
)
53+
}
54+
assert handle_to_source == {
55+
"project_information_attachment_0": "project_information",
56+
}

0 commit comments

Comments
 (0)