| name | discover-traces |
| description | This skill should be used when the user asks to discover traces, list traces, find what traces exist, inspect trace names, explore trace tags, or check trace environments in their Langfuse project. It enumerates unique trace names, tags, environments, and user IDs via the Langfuse REST API with a direct-database fallback. |
Discover Traces
Enumerate unique trace names, tags, environments, and user IDs in a Langfuse project, then summarize the tracing landscape before filtering data, building widgets, or analyzing workflows.
Prerequisites
Before executing any requests, ensure the following credentials are available (ask the user if missing):
| Variable | Example | Purpose |
|---|
HOST | http://localhost:3000 | Langfuse base URL |
PUBLIC_KEY | pk-lf-... | API Basic Auth username |
SECRET_KEY | sk-lf-... | API Basic Auth password |
PROJECT_ID | clxxxxxxxxxxxxxxxxxxxxxxxxx | Multi-tenancy project ID |
DB_CONN | postgresql://user:pass@host:5432/db | Direct DB fallback (optional) |
Validate credentials before proceeding:
curl -s -o /dev/null -w "%{http_code}" \
-u "$PUBLIC_KEY:$SECRET_KEY" \
"$HOST/api/public/traces?limit=1"
200 confirms valid authentication. 401 or 403 means the keys are incorrect; report the error and ask the user to verify them.
Step 1 -- Fetch Traces via REST API (Primary Path)
Call the Langfuse public traces endpoint from page 1 until all pages are consumed or the sample is representative.
PAGE=1
while true; do
RESPONSE=$(curl -s -u "$PUBLIC_KEY:$SECRET_KEY" \
"$HOST/api/public/traces?limit=100&page=$PAGE")
COUNT=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('data',[])))")
if [ "$COUNT" -eq 0 ]; then
break
fi
echo "$RESPONSE" >> /tmp/langfuse_traces_raw.json
PAGE=$((PAGE + 1))
done
Each page's data array contains trace objects with these relevant fields:
name -- the trace name (e.g., "chat-completion", "document-qa", "intake-generation")
tags -- array of string tags (e.g., ["production", "v2", "experiment-a"])
environment -- deployment environment string (e.g., "production", "staging", "development")
userId -- optional user identifier associated with the trace
sessionId -- optional session grouping identifier
metadata -- arbitrary metadata object attached to the trace
Process all collected pages to extract unique values:
python3 -c "
import json
from collections import Counter
traces = []
with open('/tmp/langfuse_traces_raw.json') as f:
for line in f:
try:
page = json.loads(line)
traces.extend(page.get('data', []))
except json.JSONDecodeError:
continue
# Collect unique values
names = Counter()
tags = Counter()
environments = Counter()
user_ids = set()
for t in traces:
name = t.get('name') or '(unnamed)'
names[name] += 1
for tag in (t.get('tags') or []):
tags[tag] += 1
env = t.get('environment')
if env:
environments[env] += 1
uid = t.get('userId')
if uid:
user_ids.add(uid)
print('=== TRACE NAMES ===')
for name, count in sorted(names.items(), key=lambda x: -x[1]):
print(f'| {name} | {count} |')
print()
print('=== TAGS ===')
for tag, count in sorted(tags.items(), key=lambda x: -x[1]):
print(f'| {tag} | {count} |')
print()
print('=== ENVIRONMENTS ===')
for env, count in sorted(environments.items(), key=lambda x: -x[1]):
print(f'| {env} | {count} |')
print()
print('=== USER IDS ===')
print(f'Total unique user IDs: {len(user_ids)}')
if len(user_ids) <= 20:
for uid in sorted(user_ids):
print(f' - {uid}')
else:
for uid in sorted(user_ids)[:20]:
print(f' - {uid}')
print(f' ... and {len(user_ids) - 20} more')
"
Handling Large Projects
If pagination is slow past 20 pages, stop the API loop after 20 pages. The 2000-trace sample should discover names, tags, and environments; note it is sampled and use the database fallback for exact counts when needed.
Step 2 -- Database Fallback
When the REST API is unavailable, errors, or exact counts are needed, query Langfuse PostgreSQL directly.
Option A: psycopg2 (Preferred)
Write and execute a Python script using parameterized queries:
import psycopg2
import json
conn = psycopg2.connect("CONNECTION_STRING_HERE")
cur = conn.cursor()
cur.execute("""
SELECT COALESCE(name, '(unnamed)') as trace_name, COUNT(*) as cnt
FROM traces
WHERE project_id = %s
GROUP BY trace_name
ORDER BY cnt DESC
""", (PROJECT_ID,))
print("=== TRACE NAMES ===")
for row in cur.fetchall():
print(f"| {row[0]} | {row[1]} |")
cur.execute("""
SELECT tag, COUNT(*) as cnt
FROM traces, unnest(tags) AS tag
WHERE project_id = %s
GROUP BY tag
ORDER BY cnt DESC
""", (PROJECT_ID,))
print("\n=== TAGS ===")
for row in cur.fetchall():
print(f"| {row[0]} | {row[1]} |")
cur.execute("""
SELECT COUNT(DISTINCT user_id) as unique_users
FROM traces
WHERE project_id = %s AND user_id IS NOT NULL
""", (PROJECT_ID,))
print(f"\nUnique user IDs: {cur.fetchone()[0]}")
cur.close()
conn.close()
If psycopg2 is not installed, install it first:
uv add psycopg2-binary
Option B: docker exec psql (Fallback)
When the database is only reachable from within a Docker container:
docker exec CONTAINER_NAME psql -U USER -d DBNAME -t -A -F '|' -c "
SELECT COALESCE(name, '(unnamed)'), COUNT(*)
FROM traces
WHERE project_id = 'PROJECT_ID'
GROUP BY 1
ORDER BY 2 DESC;
"
docker exec CONTAINER_NAME psql -U USER -d DBNAME -t -A -F '|' -c "
SELECT tag, COUNT(*)
FROM traces, unnest(tags) AS tag
WHERE project_id = 'PROJECT_ID'
GROUP BY tag
ORDER BY 2 DESC;
"
Parse the pipe-delimited output and format it into the standard tables.
Step 3 -- Format and Present Output
Present three markdown tables plus a user ID summary.
Trace Names
| Trace Name | Count |
|----------------------|-------|
| chat-completion | 3412 |
| document-qa | 1870 |
| intake-generation | 945 |
| (unnamed) | 23 |
Tags
| Tag | Count |
|----------------------|-------|
| production | 4200 |
| v2 | 2100 |
| experiment-a | 500 |
| debug | 12 |
Environments
Note: Environment data is available via the REST API only. The database fallback does not query environments as this column exists only in ClickHouse.
| Environment | Count |
|----------------------|-------|
| production | 5100 |
| staging | 780 |
| development | 370 |
User IDs
Report total unique user IDs; list all when 20 or fewer, otherwise list the first 20 alphabetically and the remaining count.
Step 4 -- Summary and Observations
After the tables, summarize:
- Total number of unique trace names.
- Total number of unique tags.
- Total number of distinct environments (API-only; not available via database fallback).
- Total number of unique user IDs.
- Notable patterns: unnamed traces (possible SDK misconfiguration), rare tags (possible test data), and low-count environments (possible staging/dev leftover data).
- If session IDs were observed in the API response, note whether sessions are being used and how many distinct sessions exist.
Error Handling
- Empty results: If no traces are found, report that the project has no traces and suggest verifying Langfuse SDK instrumentation and trace delivery.
- API timeout or 5xx errors: Log the error, switch to the DB fallback path, and note to the user that the API was unreachable.
- DB connection refused: Report the connection error and ask the user to verify the connection string, host reachability, and database container status.
- Permission denied on DB: Report that the database user lacks SELECT permission on the
traces table and suggest verifying the credentials.
- Malformed tags column: If the
unnest(tags) query fails, the tags column format may differ. Fall back to fetching raw tag values and parsing them in Python.
Cleanup
Remove any temporary files created during execution:
rm -f /tmp/langfuse_traces_raw.json