Salta ai contenuti

Panoramica dell’SDK Python

L’SDK Python di NextPDF è pensato per applicazioni Python che richiedono un’estrazione da PDF con origine tracciabile. Restituisce blocchi strutturati con ancoraggi di citazione, come l’indice di pagina, il livello di confidenza, i riquadri di delimitazione facoltativi e gli identificatori dei nodi semantici quando il PDF di origine espone tale struttura.

Usare l’SDK quando la pipeline deve rispondere a domande come «da quale pagina proviene questo testo?», «quale tabella supporta questo valore?» oppure «che cosa è cambiato tra questi due PDF?» senza ridurre l’estrazione da PDF a semplice testo anonimo.

  • Un client NextPDF sincrono per script, processi batch e notebook.
  • Un client AsyncNextPDF asincrono per asyncio, FastAPI e altri runtime asincroni.
  • Un’interfaccia a riga di comando (CLI) nextpdf per una singola estrazione da un percorso di file o dallo standard input, con output verso lo standard output o un file.
  • Un server Model Context Protocol (MCP) facoltativo, per consentire agli agenti di intelligenza artificiale (IA) di richiamare direttamente gli strumenti di estrazione da PDF.
  • Un backend remoto per l’uso in produzione con NextPDF Connect.
  • Un backend locale per l’estrazione offline e solo a livello di libreria tramite pypdf.

Il backend remoto invia i byte del PDF a un server NextPDF Connect. È il percorso consigliato per la produzione perché centralizza il comportamento dell’estrazione, l’autenticazione, le quote e i controlli operativi.

Il backend locale viene eseguito nel processo Python e legge i PDF tramite pypdf. È utile per lo sviluppo offline e per i PDF con tag, anche se non può fornire riquadri di delimitazione precisi e si basa su un’estrazione euristica a livello di paragrafo per i PDF senza tag. Il backend locale è disponibile solo a livello di libreria: vi si accede inserendo un LocalBackend in AsyncNextPDF. La CLI nextpdf e il server MCP non possono utilizzarlo. Per il confronto completo, vedere Matrice di scelta del backend.

L’SDK non esegue il riconoscimento ottico dei caratteri (OCR). I PDF acquisiti tramite scanner o composti solo da immagini richiedono una fase di OCR prima che NextPDF possa estrarre il testo incorporato. Anche layout complessi, testo sovrapposto e produttori di PDF non comuni possono ridurre la qualità dell’estrazione.

La CLI nextpdf funziona solo in modalità remota e non è un’interfaccia di streaming. Ogni comando legge l’intero PDF in memoria (da un percorso di file o dallo standard input), lo invia a un server NextPDF Connect, costruisce il risultato completo in memoria e quindi lo serializza con una singola scrittura. È possibile reindirizzare tale output verso un file con --output (oppure -o) o verso lo standard output, ma il risultato è interamente memorizzato nel buffer, non generato in modo incrementale. La CLI non può utilizzare il backend locale pypdf.

Entrambi i client condividono lo stesso spazio dei nomi dei metodi ast e restituiscono gli stessi modelli Pydantic. La differenza riguarda il modello di concorrenza.

ContestoDa usareMotivo
Script e processi batchNextPDF (sincrono)Flusso di controllo lineare; nessun ciclo di eventi da gestire.
Notebook JupyterNextPDF (sincrono)run_sync rileva il ciclo di eventi in esecuzione e delega la chiamata a un thread di lavoro, in modo che le chiamate bloccanti funzionino all’interno delle celle.
CLI nextpdfNextPDF (sincrono, interno)La CLI crea automaticamente un client sincrono.
asyncio (servizi)AsyncNextPDFSupporto await nativo; nessun passaggio tra thread.
FastAPI, Starlette, ASGIAsyncNextPDFCondivide il ciclo di eventi della richiesta e lo stesso pool di connessioni.
Distribuzione ad alta concorrenzaAsyncNextPDFConsente molte estrazioni contemporanee con asyncio.gather su un unico client in pool.

NextPDF incapsula un AsyncNextPDF interno ed esegue ogni chiamata tramite run_sync. All’interno di un ciclo di eventi in esecuzione (ad esempio un notebook), run_sync delega la coroutine a un thread di lavoro singolo dotato di un proprio ciclo, in modo da evitare l’errore di asyncio.run annidato. In un servizio asyncio o ASGI, richiamare direttamente AsyncNextPDF anziché sostenere il costo di tale passaggio tra thread a ogni chiamata.

Il client asincrono gestisce un httpx.AsyncClient per il pooling delle connessioni; riutilizzare quindi un’unica istanza di AsyncNextPDF e chiuderla una sola volta. Il client sincrono NextPDF non espone un metodo close(). Per carichi di lavoro asincroni di lunga durata, è preferibile usare AsyncNextPDF e gestirne il ciclo di vita in modo esplicito (vedere Modello operativo di produzione).

Un backend implementa il protocollo PdfBackend. Il backend remoto (RemoteBackend) viene selezionato automaticamente quando vengono passati base_url e api_key. Il backend locale (LocalBackend) deve essere inserito esplicitamente tramite il parametro backend= di AsyncNextPDF; non è esportato dal pacchetto nextpdf di livello superiore e non è raggiungibile dalla CLI o dal server MCP.

FunzionalitàRemoto (RemoteBackend)Locale (LocalBackend)
Selezionato dabase_url + api_keyAsyncNextPDF(backend=LocalBackend(...))
ReteNextPDF Connect tramite HyperText Transfer Protocol Secure (HTTPS)Nessuna; viene eseguito all’interno del processo
Autenticazione, quote, misurazioneCentralizzate sul serverNessuna
Osservabilità e controlli operativiLato serverNessuno
Estrazione da PDF con tag (StructTree)
Estrazione da PDF senza tagMotore del serverSuddivisione euristica dei paragrafi, confidenza 0.5
Riquadri di delimitazioneSì (quando il server li fornisce)No (bbox è None)
Estrazione di tabelle dai PDF senza tagMotore del serverNon restituisce alcuna tabella
Raggiungibile dalla CLI / dal server MCPNo (solo a livello di libreria)
Consigliato perProduzioneSviluppo offline, test su PDF con tag

Usare il backend remoto per la produzione: è l’unico percorso che offre autenticazione centralizzata, applicazione delle quote, misurazione e osservabilità. Usare il backend locale per lo sviluppo offline e i test sui PDF con tag, accettando però risultati euristici, l’assenza di riquadri di delimitazione e l’assenza di tabelle nell’input senza tag.

"""Inject the local backend for offline, library-only extraction."""
from nextpdf import AsyncNextPDF
from nextpdf.backends.local import LocalBackend
async def extract_offline(pdf_bytes: bytes) -> None:
"""Extract cited text without a NextPDF Connect server."""
async with AsyncNextPDF(backend=LocalBackend()) as client:
blocks = await client.ast.extract_cited_text(pdf_bytes)
for block in blocks:
# Heuristic blocks on untagged PDFs report confidence 0.5.
print(block.citation.confidence, block.text)

In produzione, il backend remoto viene eseguito su NextPDF Connect. I pattern riportati di seguito riguardano il riutilizzo del client, la gestione degli errori, i tentativi ripetuti, la gestione delle quote e i timeout. Ogni simbolo qui utilizzato esiste nell’SDK; l’SDK non esegue tentativi ripetuti automaticamente, pertanto il ciclo di ripetizione resta sotto la responsabilità dell’applicazione.

Riutilizzare il client e mettere in pool le connessioni

Sezione intitolata “Riutilizzare il client e mettere in pool le connessioni”

RemoteBackend mantiene un unico httpx.AsyncClient persistente per il pooling delle connessioni. Creare AsyncNextPDF una sola volta, condividerlo tra le richieste e chiuderlo all’arresto. Non creare un client per ogni richiesta.

"""Reuse one pooled async client for the lifetime of the process."""
import asyncio
import os
from pathlib import Path
from nextpdf import AsyncNextPDF
async def main() -> None:
"""Run several extractions over a single pooled client."""
base_url = os.environ["NEXTPDF_BASE_URL"]
# Treat the API key as a secret; read it from the environment, never hard-code it.
api_key = os.environ["NEXTPDF_API_KEY"]
async with AsyncNextPDF(base_url=base_url, api_key=api_key) as client:
pdf_paths = (Path("a.pdf"), Path("b.pdf"), Path("c.pdf"))
tasks = [
client.ast.get_document_ast(path.read_bytes())
for path in pdf_paths
]
documents = await asyncio.gather(*tasks)
for document in documents:
print(document.page_count, document.estimated_tokens)
if __name__ == "__main__":
asyncio.run(main())

All’uscita, il gestore di contesto asincrono richiama close(), che chiude il trasporto sottostante. Senza un gestore di contesto, richiamare manualmente await client.close().

Gestire gli errori con la gerarchia delle eccezioni

Sezione intitolata “Gestire gli errori con la gerarchia delle eccezioni”

L’SDK espone una gerarchia tipizzata di eccezioni. Tutti gli errori derivano da NextPDFError; gli errori a livello HTTP derivano da NextPDFAPIError e includono uno status_code. Intercettare i tipi specifici su cui è possibile intervenire e usare il tipo di base come fallback.

EccezioneGenerata quandoAttributi principali
NextPDFErrorTipo di base per ogni errore dell’SDKstatus_code
NextPDFAPIErrorQualsiasi errore HTTP proveniente dal serverstatus_code, error_code
NextPDFLicenseErrorHTTP 402; la funzionalità richiede un livello server superiorestatus_code (402)
QuotaExceededErrorHTTP 429; limite di frequenza o quota superatiretry_after
AstNoStructTreeErrorHTTP 422; PDF senza tag con la modalità euristica disattivatastatus_code (422)
AstBuildTimeoutErrorHTTP 504; timeout della creazione dell’ASTstatus_code (504)
"""Map SDK exceptions to caller-facing outcomes."""
from nextpdf import (
AstBuildTimeoutError,
AstNoStructTreeError,
AsyncNextPDF,
NextPDFAPIError,
NextPDFError,
NextPDFLicenseError,
QuotaExceededError,
)
async def safe_extract(client: AsyncNextPDF, pdf_bytes: bytes) -> str:
"""Extract text, translating known failures into a stable status string."""
try:
blocks = await client.ast.extract_cited_text(pdf_bytes)
except QuotaExceededError as exc:
# exc.retry_after holds the server Retry-After value in seconds, or None.
return f"rate-limited; retry after {exc.retry_after}s"
except NextPDFLicenseError:
return "feature requires a higher server tier"
except AstNoStructTreeError:
return "untagged PDF; enable heuristic mode or use a tagged PDF"
except AstBuildTimeoutError:
return "build timed out; reduce the page range"
except NextPDFAPIError as exc:
return f"server error (status {exc.status_code})"
except NextPDFError:
return "extraction failed"
return "\n".join(block.text for block in blocks)

L’SDK non esegue tentativi ripetuti automaticamente. Racchiudere le chiamate in un ciclo proprio che ritenti in caso di errori HTTP temporanei e rispetti il valore Retry-After del server, che QuotaExceededError espone come retry_after (un numero intero di secondi oppure None). Usare un backoff esponenziale per gli altri stati temporanei e non ritentare NextPDFLicenseError.

"""Retry transient failures with exponential backoff and Retry-After support."""
import asyncio
from collections.abc import Awaitable, Callable
from typing import TypeVar
from nextpdf import NextPDFAPIError, QuotaExceededError
_RETRYABLE_STATUS = frozenset({500, 502, 503, 504})
_T = TypeVar("_T")
async def with_retry(
coro_factory: Callable[[], Awaitable[_T]],
*,
max_attempts: int = 4,
) -> _T:
"""Call coro_factory() with bounded retries on transient server errors.
Args:
coro_factory: A zero-argument callable returning a fresh awaitable.
max_attempts: Maximum number of attempts before giving up.
Returns:
The awaited result of the first successful attempt.
Raises:
NextPDFAPIError: When all attempts fail or the error is not retryable.
"""
delay = 1.0
for attempt in range(1, max_attempts + 1):
try:
return await coro_factory()
except QuotaExceededError as exc:
if attempt == max_attempts:
raise
await asyncio.sleep(exc.retry_after if exc.retry_after is not None else delay)
delay *= 2.0
except NextPDFAPIError as exc:
if attempt == max_attempts or exc.status_code not in _RETRYABLE_STATUS:
raise
await asyncio.sleep(delay)
delay *= 2.0
raise RuntimeError("unreachable")

L’applicazione delle quote e dei limiti di frequenza risiede sul server. In caso di HTTP 429 l’SDK genera QuotaExceededError e analizza l’intestazione Retry-After in retry_after. Il backend remoto espone inoltre le intestazioni X-RateLimit-* nelle risposte di rendering, in modo da poter limitare la frequenza in modo proattivo prima di raggiungere un limite rigido.

Per i timeout delle richieste viene usato un valore predefinito fisso di 60 secondi complessivi, con un timeout di connessione di 10 secondi (httpx.Timeout(60.0, connect=10.0)). Per limitare le creazioni di AST lunghe, è preferibile restringere il lavoro con page_range_start, page_range_end o token_budget anziché affidarsi al solo timeout; una creazione eccessivamente lunga restituisce AstBuildTimeoutError (HTTP 504).

Un worker batch legge i PDF, estrae il testo citato e scrive un output strutturato. Riutilizzare un unico client in pool, limitare la concorrenza con un semaforo e applicare l’helper di ripetizione descritto sopra.

"""Batch-extract a directory of PDFs over one pooled async client."""
import asyncio
import os
from pathlib import Path
from nextpdf import AsyncNextPDF
async def run_batch(input_dir: Path, concurrency: int = 8) -> None:
"""Extract cited text for every PDF in input_dir, bounded by concurrency."""
semaphore = asyncio.Semaphore(concurrency)
async def worker(client: AsyncNextPDF, path: Path) -> None:
async with semaphore:
blocks = await client.ast.extract_cited_text(path.read_bytes())
out = path.with_suffix(".txt")
out.write_text("\n".join(b.text for b in blocks), encoding="utf-8")
async with AsyncNextPDF(
base_url=os.environ["NEXTPDF_BASE_URL"],
api_key=os.environ["NEXTPDF_API_KEY"],
) as client:
await asyncio.gather(*(worker(client, p) for p in input_dir.glob("*.pdf")))

Un servizio FastAPI condivide un unico AsyncNextPDF tra le richieste per l’intera durata dell’applicazione, in modo che ogni richiesta riutilizzi il pool di connessioni. Leggere le credenziali dall’ambiente e trattare la chiave API come un segreto.

"""FastAPI service that shares one pooled NextPDF client across requests."""
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, UploadFile
from nextpdf import AsyncNextPDF
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Create the pooled client on startup and close it on shutdown."""
app.state.client = AsyncNextPDF(
base_url=os.environ["NEXTPDF_BASE_URL"],
api_key=os.environ["NEXTPDF_API_KEY"],
)
try:
yield
finally:
await app.state.client.close()
app = FastAPI(lifespan=lifespan)
@app.post("/extract")
async def extract(file: UploadFile) -> dict[str, list[str]]:
"""Return cited text blocks for an uploaded PDF."""
pdf_bytes = await file.read()
blocks = await app.state.client.ast.extract_cited_text(pdf_bytes)
return {"text": [block.text for block in blocks]}

Per gli agenti IA, eseguire il server MCP. Espone strumenti per i PDF (ad esempio nextpdf_extract_text, nextpdf_extract_tables, nextpdf_get_ast, nextpdf_info, nextpdf_search, nextpdf_get_outline, nextpdf_diff e nextpdf_health) tramite standard input e standard output. Il server legge NEXTPDF_BASE_URL e NEXTPDF_API_KEY dall’ambiente ed è quindi supportato in modalità remota; come la CLI, non può utilizzare il backend locale. Installare il componente facoltativo aggiuntivo ed eseguire il modulo.

Terminal window
pip install "nextpdf[mcp]"
python -m nextpdf.mcp

Vedere Server MCP Python per la procedura dettagliata di integrazione degli agenti, CLI Python per l’uso da terminale e Documentazione di riferimento dell’API Python per la superficie completa di client, modelli ed eccezioni.