Official Python SDK for Tagnos — AI-first HITL annotation studio
Project description
tagnos-sdk
Official Python SDK for Tagnos — the AI-first human-in-the-loop annotation studio. Programmatic access to projects, schemas (TAM), items, annotations, consensus, exports, and integrations (Langfuse, …), plus a webhook receiver for real-time events.
Status: alpha. API surface may change before 1.0. Pin
tagnos<0.3in production.
Install
Requires Python 3.10+.
pip install tagnos # core SDK (sync + async)
pip install "tagnos[webhooks]" # + starlette webhook receiver
pip install "tagnos[pandas]" # + DataFrame → items adapter
pip install "tagnos[hf]" # + 🤗 datasets → items adapter
The SDK is distributed on PyPI (public) and mirrored on Google
Artifact Registry (europe-west1-python.pkg.dev/ellzx-491606/tagnos-python/)
for internal GCP consumers. Both are updated on every vX.Y.Z tag push.
To install from the private GAR mirror instead (e.g. from a Cloud Run
service in the same project), see docs/DEPLOYMENT.md.
Authentication
Generate a personal API key from https://<your-org>.tagnos.app/settings → API keys.
from tagnos import Tagnos
client = Tagnos(
org_slug="acme", # your tenant subdomain
api_key="tk_live_...", # or env TAGNOS_API_KEY
)
The SDK reads TAGNOS_API_KEY, TAGNOS_ORG_SLUG, and TAGNOS_BASE_DOMAIN (default tagnos.app) from the environment when unset.
Quickstart — sync
from tagnos import Tagnos, TamBuilder
with Tagnos() as client:
# 1. Create a project from a starter template
tam = client.schema.from_template("medical_ner")
project = client.projects.create(name="Consultations Q2", tam=tam)
# 2. Push items in bulk
client.items.import_jsonl(
project.id,
[
{"externalId": "case-001", "content": "Le patient présente…"},
{"externalId": "case-002", "content": "Pas de plainte notable."},
],
)
# 3. Iterate the full annotated queue (cursor-paginated)
for item in client.items.iter(project.id, limit=100):
print(item.id, item.status)
# 4. Export once annotations are in
client.exports.download_to(project.id, "dataset.jsonl", format="jsonl")
Quickstart — async
import asyncio
from tagnos import AsyncTagnos
async def main() -> None:
async with AsyncTagnos() as client:
# Fan-out: fetch first page of items for every project in parallel
projects = await client.projects.list()
pages = await asyncio.gather(
*(client.items.list(p.id, limit=20) for p in projects)
)
for project, page in zip(projects, pages):
print(project.name, len(page.items), "items")
asyncio.run(main())
Build a schema with TamBuilder
Tagnos projects are configured with a TAM (Tagnos Annotation Manifest) — a JSON schema that declares modality, tools (NER, classification, rating, bbox, …), label sets, relations, and the AI pipeline. TamBuilder is a fluent builder that mirrors the server-side Zod schema:
from tagnos import TamBuilder
tam = (
TamBuilder.text(name="Sentiment clients", locale="fr-FR")
.add_classification(
id="sentiment",
label="Sentiment",
values=[
{"id": "pos", "name": "Positif", "color": "#10b981"},
{"id": "neu", "name": "Neutre", "color": "#6b7280"},
{"id": "neg", "name": "Négatif", "color": "#ef4444"},
],
)
.add_rating(id="utility", label="Utilité", scale_min=1, scale_max=5)
.with_consensus(auto_accept_threshold=0.85)
.build()
)
project = client.projects.create(name="Avis clients", tam=tam)
Inspect the schema of an existing project:
from tagnos import TamInspector
payload = client.schema.get(project.id)
insp = TamInspector(payload.tam)
insp.modality # "text"
insp.tools_by_type("classification") # [{'id': 'sentiment', …}]
insp.label_ids(tool_id="sentiment") # ['pos', 'neu', 'neg']
Import data
| Method | When to use |
|---|---|
client.items.create_bulk(project_id, items) |
You already have a Python list of small dicts |
client.items.import_jsonl(project_id, rows) |
Stream many rows as JSONL (1 object / line) |
client.items.import_csv(project_id, rows) |
CSV — must have a content or text column |
client.items.import_file(project_id, path) |
Upload a .jsonl or .csv file (format auto-detected) |
dataframe_to_rows(df) (extra: pandas) |
Adapter: DataFrame → item iterator |
dataset_to_rows(ds) (extra: hf) |
Adapter: 🤗 datasets.Dataset → item iterator |
Deduplication: rows carrying an externalId are deduped server-side against already-imported items. Anonymous rows (no externalId) are always inserted.
Pagination
Every list endpoint supports cursor-based paging. Both sync and async clients expose a convenience .iter() generator that follows the cursor for you:
# sync
for annotation in client.annotations.iter(project_id, limit=200):
...
# async
async for annotation in client.annotations.iter(project_id, limit=200):
...
Webhooks
Tagnos posts webhook events (annotation.created, review.approved, item.completed, consensus.disagreement, …) to URLs you register in Settings → Webhooks. The receiver verifies the HMAC signature and dispatches to typed handlers.
from starlette.applications import Starlette
from starlette.routing import Route
from tagnos.webhooks import WebhookReceiver, AnnotationCreated
receiver = WebhookReceiver(secret="whk_...")
@receiver.on(AnnotationCreated)
async def on_annotation(evt: AnnotationCreated) -> None:
print(f"new annotation {evt.annotation_id} on item {evt.item_id}")
app = Starlette(routes=[Route("/webhooks/tagnos", receiver.asgi, methods=["POST"])])
Integrations (Langfuse, …)
from tagnos import IntegrationType # re-exported from tagnos.models
config = client.integrations.create(
type=IntegrationType.LANGFUSE,
name="Production traces",
public_key="pk-lf-…",
secret_key="sk-lf-…",
api_url="https://cloud.langfuse.com",
target_project_id=project.id,
)
client.integrations.test(config.id) # dry-run connectivity
A Cloud Scheduler job on the Tagnos side pulls new traces hourly and turns them into annotatable items in target_project_id.
Assignments (task management)
Distribute items across your team using one of three strategies
(round_robin, workload_balanced, random) and an optional due date:
members = client.members.list()
item_ids = [it.id for it in client.items.iter(project.id, limit=500)]
result = client.assignments.bulk_assign(
project.id,
item_ids=item_ids,
user_ids=[m.user_id for m in members],
strategy="workload_balanced",
due_date="2026-05-15T17:00:00Z",
)
print(f"created {result.created}/{result.total} assignments")
# iterate outstanding work for a given user
for a in client.assignments.iter(project.id, user_id="u_123", status="PENDING"):
print(a.item_id, a.due_date)
Errors
from tagnos import (
TagnosAuthError, # 401 / 403
TagnosNotFoundError, # 404
TagnosConflictError, # 409 — concurrent schema update
TagnosRateLimitError, # 429 — check .retry_after_seconds
TagnosValidationError, # 400 / 422
TagnosAPIError, # base class for all HTTP errors
)
The client auto-retries idempotent failures (429, 500, 502, 503, 504) with exponential backoff (honors Retry-After). Pass retries=0 to opt out.
Development
pip install -e ".[dev,webhooks,pandas]"
pytest
ruff check .
mypy src
Releasing
Tag-driven via Cloud Build (v*.*.*); see docs/DEPLOYMENT.md.
License
Apache-2.0
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 tagnos-0.3.1rc1.tar.gz.
File metadata
- Download URL: tagnos-0.3.1rc1.tar.gz
- Upload date:
- Size: 33.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd803c2a23c8af0b4565c754254b07461e8ffa6b4ef668d795470ade8d035587
|
|
| MD5 |
e6feffb65cc0611287780cf17ef95528
|
|
| BLAKE2b-256 |
86c0f5352c185828489020d0d0d1694b3f943d23af200380a7d00efd64283a20
|
File details
Details for the file tagnos-0.3.1rc1-py3-none-any.whl.
File metadata
- Download URL: tagnos-0.3.1rc1-py3-none-any.whl
- Upload date:
- Size: 31.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f4bf8e8eb74c0de6044d876f53beb08d8e8e2c33e564cad54b7619c70af5153
|
|
| MD5 |
51635bfffe8771c31186cc021a121a42
|
|
| BLAKE2b-256 |
27492ca0d0d69f3b7081c96a42afb6a1c685628444937292350943087483c614
|