Official Python SDK for Surfa Analytics - Ingest live traffic events
Project description
Surfa Ingest SDK
Official Python SDK for ingesting live traffic events to Surfa Analytics.
Features
- 🚀 Event Buffering - Automatic batching with configurable buffer size
- 🔄 Auto-Retry - Built-in retry logic with exponential backoff
- 📦 Context Manager - Automatic session lifecycle management
- 🏷️ Runtime Metadata - Track AI provider, model, and configuration
- ✅ Event Validation - Client-side validation before sending
- 🔍 Correlation IDs - Link related events together
- 📊 Session Tracking - Automatic session ID generation
- 🛡️ Type Safety - Full type hints and IDE autocomplete
Deterministic Metrics
When you send events using this SDK, the Surfa platform automatically calculates these metrics for each execution:
Automatically Calculated Metrics
| Metric | Definition | How It's Calculated |
|---|---|---|
| Task Completion | Whether the task was actually completed | Uses task_completed field if present, otherwise infers from event sequence |
| Total Steps | Number of agent steps taken | Count of tool call events |
| Tool Calls | Total number of tool invocations | Count of tool_call_started events |
| Retries | Repeated calls with same tool+args | Detected from tool_name + payload.input matching |
| Reattempts | Retries after a failed call | Retries where previous attempt had status: "error" |
| Redundant Calls | Retries after a successful call | Retries where previous attempt had status: "success" |
| Schema Errors | Schema validation failures | Count of schema_validation_error events |
| Hallucinated Calls | Calls to non-existent tools | Detected from error patterns |
| Recovery | Agent recovered from errors | First success after any failure |
| Total Latency | Sum of all operation latencies | Sum of latency_ms fields |
| P95 Latency | 95th percentile latency | Calculated from latency_ms distribution |
What You Need to Send
For accurate metrics, ensure your events include:
Required Fields:
kind- Event type ("tool","session","runtime")subtype- Event subtype ("call_started","call_completed", etc.)
Recommended Fields:
tool_name- For retry detectionpayload.input- Tool arguments (for retry detection)correlation_id- To pair request/response eventslatency_ms- For latency metricsstatus-"success"or"error"for completion tracking
Example:
# This event contributes to multiple metrics automatically
client.track({
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search_web",
"payload": {
"input": {"query": "AI news"} # Used for retry detection
},
"correlation_id": "abc123",
"latency_ms": 234,
"status": "success"
})
No Client-Side Calculation Needed
❌ Don't do this:
# You don't need to track retries yourself!
is_retry = check_if_retry(params) # Not needed
client.track({"is_retry": is_retry}) # Server calculates this
✅ Do this instead:
# Just send clean events - server handles the rest
client.track({
"kind": "tool",
"subtype": "call_started",
"tool_name": "search_web",
"payload": {"input": {"query": "AI news"}}
})
The platform automatically detects retries, calculates latencies, and tracks all metrics from your event stream.
Installation
pip install surfa-ingest
Quick Start
from surfa_ingest import SurfaClient
# Initialize client with your ingest key
client = SurfaClient(ingest_key="sk_live_your_key_here")
# Track events
client.track({
"kind": "tool",
"subtype": "call_started",
"tool_name": "search_web",
"args": {"query": "AI news"}
})
client.track({
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search_web",
"status": "success",
"latency_ms": 234
})
# Flush events to API
client.flush()
Context Manager (Recommended)
Use the context manager to automatically track session lifecycle:
from surfa_ingest import SurfaClient
with SurfaClient(ingest_key="sk_live_your_key_here") as client:
# Session automatically started
client.track({
"kind": "tool",
"subtype": "call_started",
"tool_name": "search_web"
})
# Session automatically ended and events flushed on exit
# task_completed automatically set based on success/failure
Explicit Task Completion
Mark whether a task was actually completed:
# Explicit success
with SurfaClient(ingest_key="sk_live_...") as client:
result = perform_task()
client.session_end(task_completed=True)
# Explicit failure
with SurfaClient(ingest_key="sk_live_...") as client:
try:
result = perform_task()
client.session_end(task_completed=True)
except Exception:
client.session_end(task_completed=False)
raise
# Automatic (context manager infers from exceptions)
with SurfaClient(ingest_key="sk_live_...") as client:
result = perform_task() # If exception: task_completed=False
# If no exception: task_completed=True
Configuration
client = SurfaClient(
ingest_key="sk_live_your_key_here",
api_url="https://api.surfa.dev", # Default: http://localhost:3000
flush_at=25, # Auto-flush after 25 events
timeout_s=10, # HTTP timeout in seconds
)
Set Runtime Metadata
Track which AI runtime is being used:
client = SurfaClient(ingest_key="sk_live_...")
client.set_runtime(
provider="anthropic",
model="claude-sonnet-4-5",
mode="messages"
)
Event Types
Tool Events
# Tool call started
client.track({
"kind": "tool",
"subtype": "call_started",
"tool_name": "search_web",
"direction": "request",
"args": {"query": "Python tutorials"}
})
# Tool call completed
client.track({
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search_web",
"direction": "response",
"status": "success",
"latency_ms": 234,
"results": [{"title": "Learn Python", "url": "..."}]
})
Session Events
# Session started
client.session_started()
# Session ended
client.session_ended()
Runtime Events
# LLM request
client.track({
"kind": "runtime",
"subtype": "llm_request",
"direction": "outbound",
"messages": [{"role": "user", "content": "Hello"}],
"temperature": 0.7
})
Event Fields
Required Fields
kind(str): Event type (e.g., "tool", "session", "runtime")
Optional Fields
subtype(str): Event subtype (e.g., "call_started", "session_ended")tool_name(str): Name of the toolstatus(str): Status (e.g., "success", "error")direction(str): Direction (e.g., "request", "response")method(str): HTTP method or similarcorrelation_id(str): Correlation ID for pairing eventsspan_parent_id(str): Parent span ID for tracinglatency_ms(int): Latency in millisecondsts(str): Timestamp (ISO 8601 format, auto-generated if not provided)- Any additional fields will be included in the event payload
Auto-Flush
Events are automatically flushed when:
- Buffer reaches
flush_atevents (default: 25) - Context manager exits
flush()is called explicitly
Error Handling
from surfa_ingest import SurfaClient, SurfaConfigError, SurfaValidationError
try:
client = SurfaClient(ingest_key="invalid_key")
except SurfaConfigError as e:
print(f"Configuration error: {e}")
try:
client.track({"invalid": "event"}) # Missing 'kind'
except SurfaValidationError as e:
print(f"Validation error: {e}")
Logging
The SDK uses Python's standard logging module:
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("surfa_ingest")
Development Status
Current Version: 0.1.0 (Alpha)
This SDK is in active development. The API may change in future versions.
Implemented
- ✅ Client initialization
- ✅ Event buffering
- ✅ Session management
- ✅ Context manager support
- ✅ Event validation
- ✅ Runtime metadata
- ✅ HTTP API integration
- ✅ Automatic retry logic (3 retries with exponential backoff)
Coming Soon
- 🔜 Background flushing
- 🔜 Async support
License
MIT
Links
- 📦 PyPI: https://pypi.org/project/surfa-ingest/
- 📚 Documentation: https://docs.surfa.dev
- 🐛 Issues: https://github.com/gamladz/surfa/issues
- 💬 Discussions: https://github.com/gamladz/surfa/discussions
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Changelog
0.1.0 (2026-02-20)
- Initial release
- Event buffering and batching
- Session management
- Context manager support
- Runtime metadata capture
- HTTP API integration with retry logic
- Event validation
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file surfa_ingest-0.1.1.tar.gz.
File metadata
- Download URL: surfa_ingest-0.1.1.tar.gz
- Upload date:
- Size: 16.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.15 {"installer":{"name":"uv","version":"0.9.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d11d3fe73c7185ac4c42b6a0607052edb27bab68de78110a325667b509d170f7
|
|
| MD5 |
b07a1b91b42822757a334c8453412a0c
|
|
| BLAKE2b-256 |
6f1d1e82cce8172310cd9a7a4007ba9ce70f7677bc7cf21eeaa58d2c110391a4
|
File details
Details for the file surfa_ingest-0.1.1-py3-none-any.whl.
File metadata
- Download URL: surfa_ingest-0.1.1-py3-none-any.whl
- Upload date:
- Size: 14.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.15 {"installer":{"name":"uv","version":"0.9.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da4939626c657d2d2578cf239a7848d44737350eaf6a687482cc1e5cec7d6a06
|
|
| MD5 |
309242537bc7a7de7d3edf27ea10eb59
|
|
| BLAKE2b-256 |
16e86e8ffd36e0aa4c3da9eec6b45b055571fd580a53dbf1341d4d7931a91ac3
|