Skip to content

release: lenz-io v1.0.0-rc.1 — first public Python SDK#1

Merged
lenzhq merged 8 commits into
mainfrom
release/v1.0.0-rc1
May 13, 2026
Merged

release: lenz-io v1.0.0-rc.1 — first public Python SDK#1
lenzhq merged 8 commits into
mainfrom
release/v1.0.0-rc1

Conversation

@lenzhq
Copy link
Copy Markdown
Owner

@lenzhq lenzhq commented May 13, 2026

Summary

  • Initial publishable Python SDK (lenz-io v1.0.0rc1)
  • Surface tightened to match the Node SDK (rc.2)
  • client.verifications.related() semantic neighbor lookup
  • visibility kwarg on verify_and_wait + batch-level visibility on verify_batch
  • README + brand polish ("Claim Verification API for AI Product Teams")

Release plan

After merge, tag v1.0.0rc1 to trigger the PyPI OIDC publish workflow.

🤖 Generated with Claude Code

Lenz and others added 8 commits May 13, 2026 01:02
Official Python client for the Lenz Hallucination Verification API. Targets
the Public API v1 surface (X-Lenz-API-Version: 2026-05-13).

Core surface
- `Lenz(api_key=...)` — top-level client with persistent httpx.Client for
  HTTP keep-alive across calls.
- Marquee verbs (top-level): verify, verify_and_wait, verify_batch,
  extract, select, get_status, usage.
- Resource namespaces (Stripe pattern): verifications.{list,get,delete,
  set_visibility}, followup.{history,send,reset}, library.{list,get}.
- Library endpoints work without an API key for sandbox exploration.

Ergonomic helpers
- `verify_and_wait(claim, timeout=120, idempotency=True)` — submits + polls
  with exponential backoff (2s/4s/8s cap 10s). Returns a typed Verification
  on success; raises LenzNeedsInputError on framing interrupt with task_id
  and payload; LenzTimeoutError carries task_id for later resume.
- Auto-idempotency on verify_and_wait so a network drop after submit
  doesn't spawn a duplicate task or charge a second credit.
- Auto-retry on 5xx + 429 with Retry-After honored (3 attempts).
- DELETE retry on 404 normalizes to True (idempotent semantics).

Errors
- Tier-2 Rust-style format with problem + cause + fix + doc_url +
  request_id on every error. Table-driven map_response_to_error in
  errors.py is the cross-language invariant (TS SDK mirrors).
- Exception hierarchy: LenzError -> LenzAuthError, LenzQuotaExceededError
  (credits_remaining), LenzValidationError (errors[]), LenzRateLimitError
  (retry_after), LenzAPIError, LenzTimeoutError (task_id),
  LenzNeedsInputError (task_id, kind, payload), LenzPipelineError
  (task_id, failure_reason), LenzWebhookSignatureError.

Webhooks
- `LenzWebhooks(secret)` stateful handler. `parse(raw_body, headers)`
  verifies HMAC-SHA256 over raw bytes (matches server's webhook_signing.py
  byte for byte), enforces a 5-minute replay window via delivered_at,
  and returns one of VerificationCompleted / VerificationFailed /
  VerificationNeedsInput / WebhookEvent.
- Low-level `verify_signature(raw_body, signature, secret)` for callers
  who want only the signature check.

Models
- Hand-written Pydantic v2 schemas mirroring lenz/api/schemas/public_api.py
  server-side: Verdict, Source, Verification, VerificationListItem,
  VerificationList, LibraryItem, LibraryList, ExtractedClaims, TaskStatus,
  TaskAccepted, BatchAccepted, Usage, FollowupHistory, FollowupReply,
  CandidateClaim, SimilarVerification, Audit, Assessment, DebateSide.
- `extra="allow"` so minor server additions don't break customer code.

Config
- `base_url=` overrideable; reads LENZ_BASE_URL env if not passed.
- `api_key=` reads LENZ_API_KEY env if not passed.
- `http_client=` injection for testing.
- `User-Agent: lenz-io-python/<version> (httpx <httpx-version>)`.
- `X-Lenz-API-Version` sent on every request.

Tests
- 67 unit tests + 3 smoke (release-only via -m smoke marker). All unit
  tests mock httpx via respx so no real network.
- Construction: env vars, base_url override, namespaces, version header.
- Marquee verbs: happy + edge cases (idempotency header, batch shape,
  extract claims).
- verify_and_wait state machine: completed, needs_input, failed, timeout,
  idempotency default, explicit key, idempotency=False.
- Resources: full CRUD on verifications/followup/library.
- Auto-retry: 503-503-200 sequence, 429 + Retry-After.
- Connection reuse: single httpx.Client across 5 calls.
- Webhook signing + replay + event discrimination + lowercase headers.
- Error mapping table: parametrized over all known statuses.

Distribution
- pyproject via hatchling. py3.9+. Tested 3.9/3.10/3.11/3.12 x ubuntu/macos
  in CI.
- GitHub Actions: ci.yml (lint + mypy strict + pytest matrix), release.yml
  (OIDC trusted publisher to PyPI on tag push, gated behind smoke tests).
- MIT licensed.
- CHANGELOG.md (keep-a-changelog), CONTRIBUTING.md, .github/ISSUE_TEMPLATE/*.

Examples
- examples/core/quickstart.py — 30s integration story.
- examples/core/verify_llm_output.py — the headline extract+verify pattern.
- examples/core/fastapi_webhook.py — runnable FastAPI receiver.

OpenAPI snapshot pinned at openapi.json; `make regen` refreshes from a
local Lenz repo. The SDK is hand-written rather than Fern-generated — the
endpoint surface is small and stable enough that hand-writing keeps the
SDK code auditable by customers and avoids vendor lock-in.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… verify_batch

Closes two related gaps in the SDK surface around claim visibility:

1. `verify_and_wait(claim=..., visibility="public")` — previously the
   kwarg existed on `_verify_submit` but wasn't surfaced via the helper.
   AI engs reaching for "make this public" found nothing to type.

2. `verify_batch(claims=[...], visibility="public")` — new batch-wide
   default that the server applies to every item unless the item has its
   own visibility override. Mirrors the existing batch-level webhook_url.

Before:
    client.verify_batch(claims=[
        {"text": "a", "visibility": "public"},
        {"text": "b", "visibility": "public"},
        {"text": "c", "visibility": "public"},
    ])

After:
    client.verify_batch(claims=[{"text": "a"}, {"text": "b"}, {"text": "c"}],
                        visibility="public")

Per-item visibility still wins when both are set. When `visibility=""`
(default) the SDK omits the field from the body so the server applies
the user's account default.

OpenAPI snapshot refreshed to reflect the new `VerifyBatchIn.visibility`
property.

Tests: 3 new cases covering verify_and_wait pass-through, batch-level
visibility in the request body, and omission when unset. 70/70 unit
tests pass (was 67).
Four changes mirror the v1.0.0-rc.2 surface tightening in the main Lenz
repo. Pre-release so no compat shim needed.

1. New `EntityRef` model and exposed at module top level. Replaces
   `list[str]` on `Verification.entities`. Carries `name` plus optional
   `qid` (Wikidata Q identifier). Customers can join Lenz output to
   their own Wikidata-indexed corpora.

2. `_extract` drops `country` and `city` kwargs. The wire body sent to
   /extract is now `{"text": ...}` only — country/city were leaked from
   the consumer product and aren't useful on the B2B SDK surface.

3. `Verification.is_time_dependent` removed. Internal cache/discovery
   signal the server no longer surfaces.

4. `Assessment.weakest_sources` / `logical_fallacies` / `missing_context`
   collapse into a single `warnings: list[str]`. Kind is implicit in
   `focus_area`. Net win: the Source Auditor's weakest_sources now reach
   customer code (the previous shape only carried two of the three).

Pydantic models stay `extra="allow"` so customers reading old fixtures
won't crash; the new shape is the documented one.

OpenAPI snapshot refreshed.

Tests
- 70/70 unit + 3 smoke skipped. Existing tests that mocked
  `verify_and_wait` responses didn't pin the old field shape; respx mocks
  validate against the relaxed Pydantic models so nothing in the suite
  needed an update.
Wraps the new `GET /verifications/{id}/related` endpoint with a typed
helper:

    related = client.verifications.related("vid_abc", limit=5)
    for r in related.items:
        print(r.verification_id, r.claim, r.verdict_label, r.distance)

Returns the public-library claims closest by pgvector ANN to a given
verification — useful for "see also" UX next to a verdict, or for
content moderation pipelines surfacing related fact-checks.

New `RelatedVerifications(items: list[SimilarVerification])` model
reuses the existing SimilarVerification shape (same as the
duplicate_found needs-input branch). Both surface from the same
server-side schema, so one Python type covers both callers.

Server-side: read-only, no LLM calls, ~10ms ANN query.
Access: any verification the caller owns + any public library item.
`limit` clamped to [1, 10] server-side; default 5.

OpenAPI snapshot refreshed.

Tests — 2 new cases in TestVerifications:
- related returns typed items + propagates `limit` query param
- empty items list when no semantic matches

72/72 unit + 3 smoke skipped pass.
…el panel)

Bumps the README intro to match the lenz.io /developers + /api/v1/docs
positioning refresh:
- Drops "fact-check API for your LLM features" framing (mis-positions us
  as runtime when our buyer is async/document-shaped).
- Leads with the two co-equal primitives (extract free + verify 7-model).
- Adds the audience qualifier (legal-memo, deep-research, due-diligence,
  vertical agents; explicitly NOT chat / voice / real-time copilots).
- Adds the canonical extract → verify code sample BEFORE the magical-
  moment "Sharks" example — matches the brief's "extract first" rule.
- Adds the "7-model panel — the work is the product" section, owning
  latency as value prop instead of apologizing for it.
- Keeps the pre-cached "Sharks don't get cancer" magical-moment demo
  intact (~1.5s response) — moved to its own section below the canonical
  integration.

Rest of the README (What you get, Webhooks, etc.) unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cation API for AI Product Teams"

Mirrors the main-repo brand rename. Updates the SDK descriptor in:
- pyproject.toml (project description)
- README.md (intro line; also retargets the linked URL from
  https://lenz.iohttps://lenz.io/developers so first-time visitors
  land on the dev-marketing page, not the consumer homepage)
- openapi.json (top-level title)
- CONTRIBUTING.md (intro line)
- src/lenz_io/__init__.py (package docstring)

"Claim verification" matches the brand voice (DESIGN.md "Voice & Copy":
say "verify" / "claim verification", not "fact-check"). The audience
qualifier "for AI Product Teams" is folded into the descriptor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the main-repo change. The "7-model panel — the work is the
product" section had two problems:
- Internal positioning prose ("the work is the product", ChatGPT
  comparison paragraph, "report you can defend") that doesn't belong
  in a developer-facing README.
- Wrong pipeline stages: real flow is Frame → Collect Evidence →
  Debate → Adjudicate → Conclude. "Cite" was never a stage; citations
  are produced as part of Conclude's output.

Renames the section to the neutral "## How verification works" with
just the architecture line + timing. No ChatGPT comparison, no marketing
rhetoric.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Line 7 (the intro paragraph) and line 12 (the audience qualifier
directly under it) both mentioned 90 seconds back-to-back. Replaced
"90 seconds is the wrong shape for those" with "pipeline runs are
the wrong shape for those" — same argument, no number-collision with
the line above.

Other ~90s mentions in the README (code comment, How verification works
section, magical-moment demo footer) stay — they live in distinct
sections and each carries different information.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lenzhq lenzhq merged commit 75eee89 into main May 13, 2026
0 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant