Skip to content

Commit 2082c18

Browse files
committed
Documentation & Readme updates
1 parent 80f0f69 commit 2082c18

5 files changed

Lines changed: 288 additions & 160 deletions

File tree

CONTRIBUTING.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Contributing
22

3-
Thank you for your interest in contributing to WildEdge Python SDK! We welcome contributions from the community.
4-
5-
## Development Setup
3+
## Development setup
64

75
1. Clone the repository:
86
```bash
@@ -25,7 +23,7 @@ Thank you for your interest in contributing to WildEdge Python SDK! We welcome c
2523
uv run tox
2624
```
2725

28-
## Code Style
26+
## Code style
2927

3028
- Use `ruff` for linting and formatting:
3129
```bash
@@ -35,19 +33,18 @@ Thank you for your interest in contributing to WildEdge Python SDK! We welcome c
3533

3634
- Follow PEP 8 and PEP 484 (type hints).
3735

38-
## Pull Request Process
36+
## Pull requests
3937

40-
1. Fork the repository and create a feature branch.
38+
1. Fork the repository and create a feature branch off `devel`.
4139
2. Make your changes and ensure tests pass.
4240
3. Update documentation if needed.
43-
4. Submit a pull request with a clear description of changes.
41+
4. Submit a pull request with a clear description of the changes.
4442

45-
## Reporting Issues
43+
## Reporting issues
4644

4745
- Use GitHub Issues for bugs and feature requests.
48-
- Provide detailed steps to reproduce bugs.
49-
- Include Python version, OS, and relevant logs.
46+
- Include steps to reproduce, Python version, OS, and relevant logs.
5047

5148
## License
5249

53-
By contributing, you agree that your contributions will be licensed under the MIT License.
50+
This project is licensed under the [Business Source License 1.1](LICENSE). By contributing, you agree that your contributions will be licensed under the same terms.

README.md

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[![Tested on Windows](https://img.shields.io/badge/tested%20on-windows-blue)](https://github.com/wildedge/wildedge-python/actions/workflows/ci.yml)
88
[![Coverage](https://codecov.io/gh/wildedge/wildedge-python/branch/main/graph/badge.svg)](https://codecov.io/gh/wildedge/wildedge-python)
99

10-
On-device ML inference monitoring for Python. Tracks latency, errors, and model metadata. No inputs or outputs captured.
10+
On-device ML inference monitoring for Python. Tracks latency, errors, and model metadata without any code modifications.
1111

1212
> **Pre-release:** The API is unstable and may change between versions.
1313
@@ -55,17 +55,34 @@ client.instrument("transformers", hubs=["huggingface"])
5555

5656
## Supported integrations
5757

58-
| Integration | Patches | Hub tracking | Example |
59-
|---|---|---|---|
60-
| `transformers` | `pipeline()`, `AutoModel.from_pretrained()` | `huggingface` | [transformers_example.py](examples/transformers_example.py) |
61-
| `mlx` | `mlx_lm.load()`, `mlx_lm.generate()` | `huggingface` | [mlx_example.py](examples/mlx_example.py) |
62-
| `timm` | `timm.create_model()` | `huggingface`, `torchhub` | [timm_example.py](examples/timm_example.py) |
63-
| `gguf` | `llama_cpp.Llama.__init__` | `huggingface` | [gguf_example.py](examples/gguf_example.py) |
64-
| `onnx` | `ort.InferenceSession` | `huggingface` | [onnx_example.py](examples/onnx_example.py) |
65-
| `ultralytics` | `ultralytics.YOLO.__init__` | - | - |
66-
| `tensorflow` | `tf.keras.models.load_model`, `tf.saved_model.load` | - | [tensorflow_example.py](examples/tensorflow_example.py) |
67-
| `torch` | forward hooks via `client.load()` | `torchhub` | [pytorch_example.py](examples/pytorch_example.py) |
68-
| `keras` | forward hooks via `client.load()` | - | [keras_example.py](examples/keras_example.py) |
58+
**On-device**
59+
60+
| Integration | Example |
61+
|---|---|
62+
| `transformers` | [transformers_example.py](examples/transformers_example.py) |
63+
| `mlx` | [mlx_example.py](examples/mlx_example.py) |
64+
| `timm` | [timm_example.py](examples/timm_example.py) |
65+
| `gguf` | [gguf_example.py](examples/gguf_example.py) |
66+
| `onnx` | [onnx_example.py](examples/onnx_example.py) |
67+
| `ultralytics` | - |
68+
| `tensorflow` | [tensorflow_example.py](examples/tensorflow_example.py) |
69+
| `torch` | [pytorch_example.py](examples/pytorch_example.py) |
70+
| `keras` | [keras_example.py](examples/keras_example.py) |
71+
72+
**Remote models**
73+
74+
| Integration | Example |
75+
|---|---|
76+
| `openai` | [openai_example.py](examples/openai_example.py) |
77+
78+
**Hub tracking**
79+
80+
Pass `hubs=` to track model download provenance. Hubs are framework-agnostic and can be combined with any integration.
81+
82+
| Hub | Tracks |
83+
|---|---|
84+
| `huggingface` | Downloads via `huggingface_hub` |
85+
| `torchhub` | Downloads via `torch.hub` |
6986

7087
For `torch` and `keras`, models are user-defined subclasses so there's no constructor to patch. Use `client.load()` to get load/unload tracking alongside inference:
7188

@@ -74,39 +91,27 @@ model = client.load(MyModel)
7491
output = model(x) # tracked automatically
7592
```
7693

77-
## Manual tracking
78-
79-
Use `@wildedge.track` as a decorator or context manager when auto-instrumentation isn't available:
80-
81-
```python
82-
handle = client.register_model(my_model)
83-
84-
@wildedge.track(handle)
85-
def run(input):
86-
return my_model.predict(input)
87-
```
94+
For unsupported frameworks, see [Manual tracking](docs/manual-tracking.md).
8895

8996
## Configuration
9097

9198
| Parameter | Default | Env var | Description |
9299
|---|---|---|---|
93100
| `dsn` | - | `WILDEDGE_DSN` | `https://<secret>@ingest.wildedge.dev/<key>` |
94101
| `app_version` | `None` | - | Your app's version string |
95-
| `app_identity` | `<project_key>` | `WILDEDGE_APP_IDENTITY` | Namespace for offline persistence; set per-app in multi-process workloads |
102+
| `app_identity` | `<project_key>` | `WILDEDGE_APP_IDENTITY` | Namespace for offline persistence. Set per-app in multi-process workloads |
96103
| `debug` | `false` | `WILDEDGE_DEBUG` | Log events to console |
97104
| `batch_size` | `10` | - | Events per transmission (1-100) |
98105
| `flush_interval_sec` | `60` | - | Max seconds between flushes (1-3600) |
99106
| `max_queue_size` | `200` | - | In-memory buffer limit (10-10000) |
100107
| `enable_offline_persistence` | `true` | - | Persist unsent events to disk and replay on restart |
101108
| `max_event_age_sec` | `900` | - | Max age before dead-lettering |
102109
| `enable_dead_letter_persistence` | `false` | - | Persist dropped batches to disk |
103-
| `sampling_interval_s` | `30.0` | `WILDEDGE_SAMPLING_INTERVAL_S` | Seconds between background hardware snapshots; `0` or `None` to disable |
110+
| `sampling_interval_s` | `30.0` | `WILDEDGE_SAMPLING_INTERVAL_S` | Seconds between background hardware snapshots. Set to `0` or `None` to disable |
104111

105112
## Privacy
106113

107-
WildEdge captures **no inputs or outputs**. Only metadata: latency, errors, model info, and download provenance. All inference runs locally; only telemetry is transmitted over HTTPS.
108-
109-
Report security issues to security@wildedge.dev.
114+
Report security & priact issues to: wildedge@googlegroups.com.
110115

111116
## Links
112117

docs/manual-tracking.md

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# Manual tracking
2+
3+
Use manual tracking when your framework is not covered by a WildEdge integration, or when you need richer metadata than auto-instrumentation provides.
4+
5+
## When to use it
6+
7+
- Your model class is custom (e.g. a `torch.nn.Module` subclass not loaded via `timm` or `transformers`)
8+
- You are calling a remote API not yet covered by an integration
9+
- You want to attach input/output metadata (token counts, image dimensions, confidence scores, etc.)
10+
- You want to record user feedback tied to a specific inference
11+
12+
## Register your model
13+
14+
Every model needs a handle before you can track events against it. Pass the model object and an explicit `model_id`:
15+
16+
```python
17+
import wildedge
18+
19+
client = wildedge.WildEdge() # set WILDEDGE_DSN env var
20+
21+
handle = client.register_model(
22+
my_model,
23+
model_id="my-org/my-model",
24+
source="local", # where the weights came from
25+
family="resnet", # optional model family
26+
version="1.0.0", # optional version string
27+
quantization="int8", # optional quantization label
28+
)
29+
```
30+
31+
For remote APIs with no local object to inspect, pass a placeholder:
32+
33+
```python
34+
handle = client.register_model(
35+
object(),
36+
model_id="openai/gpt-4o",
37+
source="https://api.openai.com",
38+
family="gpt-4o",
39+
version="2024-08-06",
40+
)
41+
```
42+
43+
`register_model` is idempotent - calling it twice with the same `model_id` returns the existing handle.
44+
45+
## Track inference
46+
47+
### Decorator
48+
49+
WildEdge times the function and records success or error automatically:
50+
51+
```python
52+
@wildedge.track(handle, input_type="text", output_type="text")
53+
def generate(prompt: str) -> str:
54+
return my_model(prompt)
55+
```
56+
57+
### Context manager
58+
59+
Use this when you need access to the result before emitting the event, or when the tracked block is not a standalone function:
60+
61+
```python
62+
with wildedge.track(handle, input_type="image", output_type="structured"):
63+
result = my_model(image_tensor)
64+
```
65+
66+
### Direct call
67+
68+
For full control over metadata - token counts, confidence scores, generation config, etc.:
69+
70+
```python
71+
from wildedge import GenerationConfig, GenerationOutputMeta, TextInputMeta
72+
from wildedge.timing import Timer
73+
74+
with Timer() as t:
75+
result = my_model(prompt)
76+
77+
inference_id = handle.track_inference(
78+
duration_ms=t.elapsed_ms,
79+
input_modality="text",
80+
output_modality="generation",
81+
success=True,
82+
input_meta=TextInputMeta(
83+
token_count=len(prompt.split()),
84+
prompt_type="chat",
85+
),
86+
output_meta=GenerationOutputMeta(
87+
tokens_in=input_tokens,
88+
tokens_out=output_tokens,
89+
tokens_per_second=round(output_tokens / t.elapsed_ms * 1000, 1),
90+
stop_reason="stop",
91+
),
92+
generation_config=GenerationConfig(
93+
temperature=0.7,
94+
max_tokens=512,
95+
),
96+
)
97+
```
98+
99+
`track_inference` returns an `inference_id` string you can use to attach feedback later.
100+
101+
## Input and output metadata
102+
103+
All fields are optional.
104+
105+
### Text input (`TextInputMeta`)
106+
107+
| Field | Type | Description |
108+
|---|---|---|
109+
| `char_count` | `int` | Character count |
110+
| `word_count` | `int` | Word count |
111+
| `token_count` | `int` | Token count |
112+
| `language` | `str` | BCP-47 language code |
113+
| `prompt_type` | `str` | e.g. `"chat"`, `"completion"`, `"instruct"` |
114+
| `turn_index` | `int` | Position in a multi-turn conversation |
115+
| `contains_code` | `bool` | Whether the input contains code |
116+
117+
### Image input (`ImageInputMeta`)
118+
119+
| Field | Type | Description |
120+
|---|---|---|
121+
| `width` | `int` | Pixel width |
122+
| `height` | `int` | Pixel height |
123+
| `channels` | `int` | Channel count |
124+
| `format` | `str` | e.g. `"jpeg"`, `"png"`, `"rgb"` |
125+
| `source` | `str` | e.g. `"camera"`, `"file"`, `"stream"` |
126+
127+
### Audio input (`AudioInputMeta`)
128+
129+
| Field | Type | Description |
130+
|---|---|---|
131+
| `duration_ms` | `int` | Audio length |
132+
| `sample_rate` | `int` | Hz |
133+
| `channels` | `int` | Channel count |
134+
| `format` | `str` | e.g. `"wav"`, `"mp3"` |
135+
| `is_streaming` | `bool` | Live stream vs pre-recorded |
136+
137+
### Generation output (`GenerationOutputMeta`)
138+
139+
| Field | Type | Description |
140+
|---|---|---|
141+
| `tokens_in` | `int` | Prompt token count |
142+
| `tokens_out` | `int` | Completion token count |
143+
| `tokens_per_second` | `float` | Generation throughput |
144+
| `cached_input_tokens` | `int` | Tokens served from KV cache |
145+
| `reasoning_tokens_out` | `int` | Reasoning/thinking tokens (e.g. o1) |
146+
| `time_to_first_token_ms` | `int` | TTFT latency |
147+
| `stop_reason` | `str` | e.g. `"stop"`, `"length"`, `"content_filter"` |
148+
149+
### Classification output (`ClassificationOutputMeta`)
150+
151+
| Field | Type | Description |
152+
|---|---|---|
153+
| `num_predictions` | `int` | Number of predictions returned |
154+
| `avg_confidence` | `float` | Mean confidence across predictions |
155+
| `top_k` | `list[TopKPrediction]` | Top-k labels with confidence scores |
156+
157+
### Detection output (`DetectionOutputMeta`)
158+
159+
| Field | Type | Description |
160+
|---|---|---|
161+
| `num_predictions` | `int` | Number of detections |
162+
| `avg_confidence` | `float` | Mean confidence |
163+
| `top_k` | `list[TopKPrediction]` | Top detections with bounding boxes |
164+
165+
### Embedding output (`EmbeddingOutputMeta`)
166+
167+
| Field | Type | Description |
168+
|---|---|---|
169+
| `dimensions` | `int` | Embedding vector size |
170+
171+
## Track errors
172+
173+
```python
174+
try:
175+
result = my_model(input)
176+
except Exception as exc:
177+
handle.track_error(
178+
error_code="UNKNOWN",
179+
error_message=str(exc),
180+
)
181+
raise
182+
```
183+
184+
`wildedge.track` (decorator and context manager) captures errors automatically when `capture_errors=True` (the default).
185+
186+
## Track feedback
187+
188+
Link user or automated feedback to a specific inference via the `inference_id` returned by `track_inference`:
189+
190+
```python
191+
from wildedge import FeedbackType
192+
193+
inference_id = handle.track_inference(duration_ms=..., ...)
194+
195+
# Later, when feedback is available
196+
handle.track_feedback(inference_id, FeedbackType.THUMBS_UP)
197+
```
198+
199+
If you always want to attach feedback to the most recent inference on a handle, use the shorthand:
200+
201+
```python
202+
handle.feedback(FeedbackType.THUMBS_DOWN)
203+
```
204+
205+
`FeedbackType` values: `THUMBS_UP`, `THUMBS_DOWN`.

0 commit comments

Comments
 (0)