scope-analytics 0.1.0__tar.gz → 0.1.1__tar.gz

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.
Files changed (25) hide show
  1. {scope_analytics-0.1.0/scope_analytics.egg-info → scope_analytics-0.1.1}/PKG-INFO +1 -1
  2. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/context.py +69 -0
  3. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/events.py +13 -0
  4. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/middleware.py +51 -0
  5. {scope_analytics-0.1.0 → scope_analytics-0.1.1/scope_analytics.egg-info}/PKG-INFO +1 -1
  6. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/setup.py +1 -1
  7. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/LICENSE +0 -0
  8. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/MANIFEST.in +0 -0
  9. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/README.md +0 -0
  10. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/__init__.py +0 -0
  11. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/auto.py +0 -0
  12. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/cli.py +0 -0
  13. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/client.py +0 -0
  14. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/config.py +0 -0
  15. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/patches/__init__.py +0 -0
  16. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/patches/anthropic_patch.py +0 -0
  17. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/patches/gemini_patch.py +0 -0
  18. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/patches/openai_patch.py +0 -0
  19. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics/queue.py +0 -0
  20. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics.egg-info/SOURCES.txt +0 -0
  21. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics.egg-info/dependency_links.txt +0 -0
  22. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics.egg-info/entry_points.txt +0 -0
  23. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics.egg-info/requires.txt +0 -0
  24. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/scope_analytics.egg-info/top_level.txt +0 -0
  25. {scope_analytics-0.1.0 → scope_analytics-0.1.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scope-analytics
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: AI-powered analytics SDK for backend applications with automatic LLM tracking
5
5
  Home-page: https://github.com/scopeai/scope-analytics-python
6
6
  Author: Scope AI
@@ -17,6 +17,23 @@ user_id_context: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
17
17
  'scope_user_id', default=None
18
18
  )
19
19
 
20
+ # Request context for LLM event correlation
21
+ url_context: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
22
+ 'scope_url', default=None
23
+ )
24
+
25
+ referrer_context: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
26
+ 'scope_referrer', default=None
27
+ )
28
+
29
+ thread_id_context: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
30
+ 'scope_thread_id', default=None
31
+ )
32
+
33
+ user_agent_context: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
34
+ 'scope_user_agent', default=None
35
+ )
36
+
20
37
  # Recursion guard - prevents infinite loops when Scope tracks its own LLM calls
21
38
  # This is critical when Scope AI backend uses Scope SDK to track itself
22
39
  _in_scope_context: contextvars.ContextVar[bool] = contextvars.ContextVar(
@@ -72,6 +89,50 @@ class ScopeContext:
72
89
  """
73
90
  return user_id_context.get()
74
91
 
92
+ # =========================================================================
93
+ # Request Context - URL, Referrer, Thread ID, User Agent
94
+ # =========================================================================
95
+
96
+ @staticmethod
97
+ def set_url(url: str) -> None:
98
+ """Set the request URL for the current context"""
99
+ url_context.set(url)
100
+
101
+ @staticmethod
102
+ def get_url() -> Optional[str]:
103
+ """Get the request URL from current context"""
104
+ return url_context.get()
105
+
106
+ @staticmethod
107
+ def set_referrer(referrer: str) -> None:
108
+ """Set the referrer for the current context"""
109
+ referrer_context.set(referrer)
110
+
111
+ @staticmethod
112
+ def get_referrer() -> Optional[str]:
113
+ """Get the referrer from current context"""
114
+ return referrer_context.get()
115
+
116
+ @staticmethod
117
+ def set_thread_id(thread_id: str) -> None:
118
+ """Set the thread ID for the current context (for ChatGPT-style apps)"""
119
+ thread_id_context.set(thread_id)
120
+
121
+ @staticmethod
122
+ def get_thread_id() -> Optional[str]:
123
+ """Get the thread ID from current context"""
124
+ return thread_id_context.get()
125
+
126
+ @staticmethod
127
+ def set_user_agent(user_agent: str) -> None:
128
+ """Set the user agent for the current context"""
129
+ user_agent_context.set(user_agent)
130
+
131
+ @staticmethod
132
+ def get_user_agent() -> Optional[str]:
133
+ """Get the user agent from current context"""
134
+ return user_agent_context.get()
135
+
75
136
  @staticmethod
76
137
  def clear() -> None:
77
138
  """
@@ -80,6 +141,10 @@ class ScopeContext:
80
141
  """
81
142
  session_id_context.set(None)
82
143
  user_id_context.set(None)
144
+ url_context.set(None)
145
+ referrer_context.set(None)
146
+ thread_id_context.set(None)
147
+ user_agent_context.set(None)
83
148
 
84
149
  @staticmethod
85
150
  def generate_temp_session_id() -> str:
@@ -116,6 +181,10 @@ class ScopeContext:
116
181
  return {
117
182
  "session_id": session_id_context.get(),
118
183
  "user_id": user_id_context.get(),
184
+ "url": url_context.get(),
185
+ "referrer": referrer_context.get(),
186
+ "thread_id": thread_id_context.get(),
187
+ "user_agent": user_agent_context.get(),
119
188
  }
120
189
 
121
190
  # =========================================================================
@@ -46,6 +46,12 @@ class EventFormatter:
46
46
  session_id = ScopeContext.get_session_id()
47
47
  user_id = ScopeContext.get_user_id()
48
48
 
49
+ # Get request context for conversation correlation
50
+ url = ScopeContext.get_url()
51
+ referrer = ScopeContext.get_referrer()
52
+ thread_id = ScopeContext.get_thread_id()
53
+ user_agent = ScopeContext.get_user_agent()
54
+
49
55
  # Determine if this is a user-facing LLM call
50
56
  # If we have a session_id from frontend (not temp_), it's user-facing
51
57
  is_user_facing = session_id and not session_id.startswith("temp_")
@@ -64,6 +70,13 @@ class EventFormatter:
64
70
  "session_id": session_id or ScopeContext.ensure_session_id(),
65
71
  "user_id": user_id,
66
72
 
73
+ # Request context for conversation correlation
74
+ # This enables the fallback chain: thread_id → session_id+url → session_id
75
+ "url": url,
76
+ "referrer": referrer,
77
+ "thread_id": thread_id, # For ChatGPT-style apps with multiple threads
78
+ "user_agent": user_agent,
79
+
67
80
  # LLM call specific fields
68
81
  "provider": provider,
69
82
  "model": model,
@@ -199,6 +199,48 @@ def extract_session_from_headers(headers: dict) -> Optional[str]:
199
199
  return None
200
200
 
201
201
 
202
+ def _set_request_context(headers: dict, path: str = None) -> None:
203
+ """
204
+ Set request context variables from HTTP headers.
205
+ This context is used to enrich LLM events with request information.
206
+
207
+ Args:
208
+ headers: Dictionary of HTTP headers
209
+ path: Request path (URL)
210
+ """
211
+ # Set URL (request path)
212
+ if path:
213
+ ScopeContext.set_url(path)
214
+
215
+ # Extract and set referrer (case-insensitive)
216
+ referrer = None
217
+ for key, value in headers.items():
218
+ key_lower = key.lower()
219
+ if key_lower == "referer" or key_lower == "referrer":
220
+ referrer = value
221
+ break
222
+ if referrer:
223
+ ScopeContext.set_referrer(referrer)
224
+
225
+ # Extract and set thread_id (X-Scope-Thread-ID header for ChatGPT-style apps)
226
+ thread_id = None
227
+ for key, value in headers.items():
228
+ if key.lower() == "x-scope-thread-id":
229
+ thread_id = value
230
+ break
231
+ if thread_id:
232
+ ScopeContext.set_thread_id(thread_id)
233
+
234
+ # Extract and set user_agent
235
+ user_agent = None
236
+ for key, value in headers.items():
237
+ if key.lower() == "user-agent":
238
+ user_agent = value
239
+ break
240
+ if user_agent:
241
+ ScopeContext.set_user_agent(user_agent)
242
+
243
+
202
244
  # =============================================================================
203
245
  # FastAPI / Starlette Middleware (ASGI)
204
246
  # =============================================================================
@@ -278,6 +320,9 @@ class ScopeSessionMiddleware:
278
320
  # Extract request details for tracking
279
321
  method = scope.get("method", "GET")
280
322
  path = scope.get("path", "/")
323
+
324
+ # Set request context (URL, referrer, thread_id, user_agent) for LLM events
325
+ _set_request_context(headers, path)
281
326
  query_string = scope.get("query_string", b"").decode("utf-8", errors="ignore")
282
327
  query_params = dict(pair.split("=", 1) for pair in query_string.split("&") if "=" in pair) if query_string else {}
283
328
 
@@ -407,6 +452,9 @@ class FlaskScopeMiddleware:
407
452
  # Extract request details for tracking
408
453
  method = environ.get("REQUEST_METHOD", "GET")
409
454
  path = environ.get("PATH_INFO", "/")
455
+
456
+ # Set request context (URL, referrer, thread_id, user_agent) for LLM events
457
+ _set_request_context(headers, path)
410
458
  query_string = environ.get("QUERY_STRING", "")
411
459
  query_params = dict(pair.split("=", 1) for pair in query_string.split("&") if "=" in pair) if query_string else {}
412
460
 
@@ -631,6 +679,9 @@ class DjangoScopeMiddleware:
631
679
  # Extract request details for tracking
632
680
  method = request.method
633
681
  path = request.path
682
+
683
+ # Set request context (URL, referrer, thread_id, user_agent) for LLM events
684
+ _set_request_context(django_headers, path)
634
685
  query_params = dict(request.GET.items()) if hasattr(request, 'GET') else {}
635
686
 
636
687
  # Extract useful headers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scope-analytics
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: AI-powered analytics SDK for backend applications with automatic LLM tracking
5
5
  Home-page: https://github.com/scopeai/scope-analytics-python
6
6
  Author: Scope AI
@@ -11,7 +11,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
11
11
 
12
12
  setup(
13
13
  name="scope-analytics",
14
- version="0.1.0",
14
+ version="0.1.1",
15
15
  author="Scope AI",
16
16
  author_email="support@scopeai.dev",
17
17
  description="AI-powered analytics SDK for backend applications with automatic LLM tracking",
File without changes