Spaces:
Sleeping
Sleeping
Rafael Poyiadzi Claude Opus 4.6 commited on
Commit ·
59ff43a
1
Parent(s): 379a533
Add PostHog analytics with graceful degradation
Browse filesServer-side tracking (started/completed/failed) and client-side JS snippet,
gated behind _POSTHOG_ENABLED so calls are skipped when env vars are unset.
API keys are hashed for distinct_id. Also cleans up unused _extract_answer
and RESEARCH_TRUNCATE_LEN.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- app.py +53 -11
- requirements.txt +1 -0
app.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import ast
|
|
|
|
| 2 |
import json
|
| 3 |
import os
|
| 4 |
import tempfile
|
|
@@ -6,8 +7,23 @@ from typing import Literal
|
|
| 6 |
|
| 7 |
import gradio as gr
|
| 8 |
import pandas as pd
|
|
|
|
| 9 |
from pydantic import BaseModel, Field, create_model
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
from everyrow.ops import agent_map
|
| 12 |
from everyrow.task import EffortLevel
|
| 13 |
|
|
@@ -107,6 +123,11 @@ def fields_to_schema_json(fields_list: list[dict]) -> str:
|
|
| 107 |
return json.dumps(clean, indent=2) if clean else ""
|
| 108 |
|
| 109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
async def run_agent_map(api_key, file, query, effort_label, fields_list):
|
| 111 |
if not api_key:
|
| 112 |
raise gr.Error("Please enter your everyrow API key.")
|
|
@@ -122,6 +143,7 @@ async def run_agent_map(api_key, file, query, effort_label, fields_list):
|
|
| 122 |
raise gr.Error("The uploaded CSV is empty.")
|
| 123 |
|
| 124 |
effort_level = EFFORT_LEVELS[effort_label]
|
|
|
|
| 125 |
|
| 126 |
try:
|
| 127 |
response_model = build_response_model(fields_list)
|
|
@@ -132,11 +154,41 @@ async def run_agent_map(api_key, file, query, effort_label, fields_list):
|
|
| 132 |
if response_model is not None:
|
| 133 |
kwargs["response_model"] = response_model
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
result = await agent_map(**kwargs)
|
| 136 |
|
| 137 |
if result.error:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
raise gr.Error(f"agent_map failed: {result.error}")
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
output_df = result.data
|
| 141 |
|
| 142 |
if "research" in output_df.columns:
|
|
@@ -151,9 +203,6 @@ async def run_agent_map(api_key, file, query, effort_label, fields_list):
|
|
| 151 |
return output_df, gr.update(value=tmp.name, visible=True)
|
| 152 |
|
| 153 |
|
| 154 |
-
RESEARCH_TRUNCATE_LEN = 120
|
| 155 |
-
|
| 156 |
-
|
| 157 |
def _parse_research_val(val):
|
| 158 |
"""Try to parse a research value into a dict."""
|
| 159 |
if isinstance(val, dict):
|
|
@@ -168,14 +217,6 @@ def _parse_research_val(val):
|
|
| 168 |
return None
|
| 169 |
|
| 170 |
|
| 171 |
-
def _extract_answer(val):
|
| 172 |
-
"""Extract the answer string from a research value."""
|
| 173 |
-
parsed = _parse_research_val(val)
|
| 174 |
-
if parsed is not None:
|
| 175 |
-
return parsed.get("answer", str(val))
|
| 176 |
-
return str(val)
|
| 177 |
-
|
| 178 |
-
|
| 179 |
def hide_research(df: pd.DataFrame) -> pd.DataFrame:
|
| 180 |
"""Default view: drop the research column."""
|
| 181 |
if "research" not in df.columns:
|
|
@@ -203,6 +244,7 @@ def expand_research(df: pd.DataFrame) -> pd.DataFrame:
|
|
| 203 |
|
| 204 |
with gr.Blocks(
|
| 205 |
title="everyrow annotate – AI Data Annotation & Web Research",
|
|
|
|
| 206 |
css=".error-box { background: #fee; border: 1px solid #c00; border-radius: 8px; padding: 12px; color: #900; }",
|
| 207 |
) as demo:
|
| 208 |
gr.Markdown(
|
|
|
|
| 1 |
import ast
|
| 2 |
+
import hashlib
|
| 3 |
import json
|
| 4 |
import os
|
| 5 |
import tempfile
|
|
|
|
| 7 |
|
| 8 |
import gradio as gr
|
| 9 |
import pandas as pd
|
| 10 |
+
import posthog
|
| 11 |
from pydantic import BaseModel, Field, create_model
|
| 12 |
|
| 13 |
+
_POSTHOG_KEY = os.environ.get("POSTHOG_KEY", "")
|
| 14 |
+
_POSTHOG_HOST = os.environ.get("POSTHOG_HOST", "")
|
| 15 |
+
_POSTHOG_ENABLED = bool(_POSTHOG_KEY and _POSTHOG_HOST)
|
| 16 |
+
|
| 17 |
+
posthog.project_api_key = _POSTHOG_KEY
|
| 18 |
+
posthog.host = _POSTHOG_HOST
|
| 19 |
+
|
| 20 |
+
POSTHOG_HEAD = f"""
|
| 21 |
+
<script>
|
| 22 |
+
!function(t,e){{var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){{function g(t,e){{var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){{t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){{var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e}},u.people.toString=function(){{return u.toString(1)+".people (stub)"}},o="init capture register register_once unregister opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing identify alias people.set people.set_once set_config reset opt_in_capturing".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])}},e.__SV=1)}}(document,window.posthog||[]);
|
| 23 |
+
posthog.init('{_POSTHOG_KEY}',{{api_host:'{_POSTHOG_HOST}', person_profiles: 'identified_only'}})
|
| 24 |
+
</script>
|
| 25 |
+
""" if _POSTHOG_ENABLED else ""
|
| 26 |
+
|
| 27 |
from everyrow.ops import agent_map
|
| 28 |
from everyrow.task import EffortLevel
|
| 29 |
|
|
|
|
| 123 |
return json.dumps(clean, indent=2) if clean else ""
|
| 124 |
|
| 125 |
|
| 126 |
+
def _posthog_distinct_id(api_key: str) -> str:
|
| 127 |
+
"""Hash the API key to use as a stable distinct_id without storing the secret."""
|
| 128 |
+
return hashlib.sha256(api_key.encode()).hexdigest()[:16]
|
| 129 |
+
|
| 130 |
+
|
| 131 |
async def run_agent_map(api_key, file, query, effort_label, fields_list):
|
| 132 |
if not api_key:
|
| 133 |
raise gr.Error("Please enter your everyrow API key.")
|
|
|
|
| 143 |
raise gr.Error("The uploaded CSV is empty.")
|
| 144 |
|
| 145 |
effort_level = EFFORT_LEVELS[effort_label]
|
| 146 |
+
distinct_id = _posthog_distinct_id(api_key)
|
| 147 |
|
| 148 |
try:
|
| 149 |
response_model = build_response_model(fields_list)
|
|
|
|
| 154 |
if response_model is not None:
|
| 155 |
kwargs["response_model"] = response_model
|
| 156 |
|
| 157 |
+
if _POSTHOG_ENABLED:
|
| 158 |
+
posthog.capture(
|
| 159 |
+
distinct_id=distinct_id,
|
| 160 |
+
event="agent_map_started",
|
| 161 |
+
properties={
|
| 162 |
+
"effort_level": effort_label,
|
| 163 |
+
"row_count": len(df),
|
| 164 |
+
"column_count": len(df.columns),
|
| 165 |
+
"field_count": len(fields_list) if fields_list else 0,
|
| 166 |
+
"app": "everyrow-research-space",
|
| 167 |
+
},
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
result = await agent_map(**kwargs)
|
| 171 |
|
| 172 |
if result.error:
|
| 173 |
+
if _POSTHOG_ENABLED:
|
| 174 |
+
posthog.capture(
|
| 175 |
+
distinct_id=distinct_id,
|
| 176 |
+
event="agent_map_failed",
|
| 177 |
+
properties={"error": str(result.error), "app": "everyrow-research-space"},
|
| 178 |
+
)
|
| 179 |
raise gr.Error(f"agent_map failed: {result.error}")
|
| 180 |
|
| 181 |
+
if _POSTHOG_ENABLED:
|
| 182 |
+
posthog.capture(
|
| 183 |
+
distinct_id=distinct_id,
|
| 184 |
+
event="agent_map_completed",
|
| 185 |
+
properties={
|
| 186 |
+
"output_rows": len(result.data),
|
| 187 |
+
"output_columns": len(result.data.columns),
|
| 188 |
+
"app": "everyrow-research-space",
|
| 189 |
+
},
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
output_df = result.data
|
| 193 |
|
| 194 |
if "research" in output_df.columns:
|
|
|
|
| 203 |
return output_df, gr.update(value=tmp.name, visible=True)
|
| 204 |
|
| 205 |
|
|
|
|
|
|
|
|
|
|
| 206 |
def _parse_research_val(val):
|
| 207 |
"""Try to parse a research value into a dict."""
|
| 208 |
if isinstance(val, dict):
|
|
|
|
| 217 |
return None
|
| 218 |
|
| 219 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
def hide_research(df: pd.DataFrame) -> pd.DataFrame:
|
| 221 |
"""Default view: drop the research column."""
|
| 222 |
if "research" not in df.columns:
|
|
|
|
| 244 |
|
| 245 |
with gr.Blocks(
|
| 246 |
title="everyrow annotate – AI Data Annotation & Web Research",
|
| 247 |
+
head=POSTHOG_HEAD,
|
| 248 |
css=".error-box { background: #fee; border: 1px solid #c00; border-radius: 8px; padding: 12px; color: #900; }",
|
| 249 |
) as demo:
|
| 250 |
gr.Markdown(
|
requirements.txt
CHANGED
|
@@ -1 +1,2 @@
|
|
| 1 |
everyrow>=0.2.0
|
|
|
|
|
|
| 1 |
everyrow>=0.2.0
|
| 2 |
+
posthog>=3.0.0
|