Descripción general del SDK de Python
Descripción general del SDK de Python
Sección titulada «Descripción general del SDK de Python»El SDK de Python de NextPDF está pensado para aplicaciones de Python que necesitan extracción de PDF con procedencia. Devuelve bloques estructurados con anclas de cita, como índice de página, confianza, cuadros delimitadores opcionales e identificadores de nodos semánticos, cuando el PDF de origen expone esa estructura.
Conviene usar el SDK cuando la canalización debe responder preguntas como «¿de qué página proviene este texto?», «¿qué tabla respalda este valor?» o «¿qué cambió entre estos dos PDF?» sin reducir la extracción de PDF a texto plano anónimo.
Qué ofrece
Sección titulada «Qué ofrece»- Un cliente
NextPDFsíncrono para scripts, trabajos por lotes y notebooks. - Un cliente
AsyncNextPDFasíncrono paraasyncio, FastAPI y otros entornos de ejecución asíncronos. - Una interfaz de línea de comandos (CLI)
nextpdfpara extracciones puntuales desde una ruta de archivo o desde la entrada estándar, con salida a la salida estándar o a un archivo. - Un servidor opcional Model Context Protocol (MCP) que permite a los agentes de inteligencia artificial (IA) llamar directamente a las herramientas de extracción de PDF.
- Un backend remoto para uso en producción con NextPDF Connect.
- Un backend local para extracción sin conexión, disponible solo como biblioteca a través de
pypdf.
Opciones de backend
Sección titulada «Opciones de backend»El backend remoto envía los bytes del PDF a un servidor NextPDF Connect. Esta es la ruta de producción recomendada porque centraliza el comportamiento de extracción, la autenticación, las cuotas y los controles operativos.
El backend local se ejecuta dentro del proceso de Python y lee los PDF a través de pypdf. Es útil para el desarrollo sin conexión y para PDF etiquetados, aunque no puede ofrecer cuadros delimitadores precisos y usa una extracción heurística a nivel de párrafo para los PDF sin etiquetar. El backend local solo está disponible como biblioteca: se accede a él inyectando un LocalBackend en AsyncNextPDF. La CLI nextpdf y el servidor MCP no pueden usarlo. La comparación completa está en Matriz de elección de backend.
Limitaciones
Sección titulada «Limitaciones»El SDK no realiza reconocimiento óptico de caracteres (OCR). Los PDF escaneados o compuestos solo por imágenes necesitan un paso de OCR antes de que NextPDF pueda extraer el texto incrustado. Los diseños complejos, el texto superpuesto y los productores de PDF poco comunes también pueden reducir la calidad de la extracción.
La CLI nextpdf solo funciona en modo remoto y no es una interfaz de streaming. Cada comando lee todo el PDF en memoria (desde una ruta de archivo o desde la entrada estándar), lo envía a un servidor NextPDF Connect, construye el resultado completo en memoria y luego lo serializa en una sola escritura. Es posible redirigir esa salida a un archivo con --output (o -o) o a la salida estándar, pero el resultado se almacena por completo en el búfer; no se produce de forma incremental. La CLI no puede usar el backend local pypdf.
Cómo elegir un cliente: síncrono o asíncrono
Sección titulada «Cómo elegir un cliente: síncrono o asíncrono»Ambos clientes comparten el mismo espacio de nombres de métodos ast y devuelven los mismos modelos de Pydantic. La diferencia está en el modelo de concurrencia.
| Contexto | Usar | Motivo |
|---|---|---|
| Scripts y trabajos por lotes | NextPDF (síncrono) | Flujo de control lineal; sin bucle de eventos que gestionar. |
| Notebooks de Jupyter | NextPDF (síncrono) | run_sync detecta el bucle de eventos en ejecución y despacha el trabajo a un hilo de trabajo, de modo que las llamadas bloqueantes funcionan dentro de las celdas. |
La interfaz nextpdf (CLI) | NextPDF (síncrono, interno) | La CLI construye internamente un cliente síncrono. |
asyncio (servicios) | AsyncNextPDF | Uso nativo de await; sin transferencia entre hilos. |
| FastAPI, Starlette, ASGI | AsyncNextPDF | Comparte el bucle de eventos de la solicitud y el mismo grupo de conexiones. |
| Distribución de alta concurrencia | AsyncNextPDF | Permite ejecutar muchas extracciones de forma concurrente con asyncio.gather sobre un único cliente con grupo de conexiones. |
NextPDF envuelve un AsyncNextPDF interno y ejecuta cada llamada a través de run_sync. Dentro de un bucle de eventos en ejecución (por ejemplo, un notebook), run_sync despacha la corrutina a un único hilo de trabajo con su propio bucle, lo que evita el error de asyncio.run anidado. En un servicio asyncio o ASGI, conviene llamar directamente a AsyncNextPDF en lugar de incurrir en esa transferencia entre hilos en cada llamada.
El cliente asíncrono mantiene un httpx.AsyncClient para la agrupación de conexiones, por lo que se debe reutilizar una sola instancia de AsyncNextPDF y cerrarla una sola vez. El cliente síncrono NextPDF no expone un método close(). Para cargas de trabajo asíncronas de larga duración, conviene preferir AsyncNextPDF y gestionar su ciclo de vida de forma explícita (consulta Modelo operativo de producción).
Matriz de elección de backend
Sección titulada «Matriz de elección de backend»Un backend implementa el protocolo PdfBackend. El backend remoto (RemoteBackend) se selecciona automáticamente cuando pasas base_url y api_key. El backend local (LocalBackend) debe inyectarse explícitamente a través del parámetro backend= de AsyncNextPDF; no se exporta desde el paquete de nivel superior nextpdf y no es accesible desde la CLI ni desde el servidor MCP.
| Capacidad | Remoto (RemoteBackend) | Local (LocalBackend) |
|---|---|---|
| Seleccionado por | base_url + api_key | AsyncNextPDF(backend=LocalBackend(...)) |
| Red | NextPDF Connect mediante el protocolo seguro de transferencia de hipertexto (HTTPS) | Ninguna; se ejecuta dentro del proceso |
| Autenticación, cuotas, medición | Centralizadas en el servidor | Ninguna |
| Observabilidad y controles operativos | En el servidor | Ninguna |
| Extracción de PDF etiquetado (StructTree) | Sí | Sí |
| Extracción de PDF sin etiquetar | Motor del servidor | División heurística de párrafos, confianza 0.5 |
| Cuadros delimitadores | Sí (cuando el servidor los proporciona) | No (bbox es None) |
| Extracción de tablas en PDF sin etiquetar | Motor del servidor | No devuelve tablas |
| Accesible desde la CLI / el servidor MCP | Sí | No (solo de biblioteca) |
| Recomendado para | Producción | Desarrollo sin conexión, pruebas con PDF etiquetados |
Usar el backend remoto para producción: es la única ruta que ofrece autenticación centralizada, aplicación de cuotas, medición y observabilidad. Usar el backend local para el desarrollo sin conexión y las pruebas con PDF etiquetados, teniendo en cuenta que los resultados son heurísticos, sin cuadros delimitadores y sin tablas en la entrada sin etiquetar.
"""Inject the local backend for offline, library-only extraction."""
from nextpdf import AsyncNextPDFfrom 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)Modelo operativo de producción
Sección titulada «Modelo operativo de producción»En producción se ejecuta el backend remoto contra NextPDF Connect. Los patrones siguientes cubren la reutilización del cliente, el manejo de errores, los reintentos, la gestión de cuotas y los tiempos de espera. Cada símbolo que se usa aquí existe en el SDK; el SDK no realiza reintentos automáticamente, por lo que el bucle de reintentos queda a cargo de la aplicación.
Reutilizar el cliente y agrupar las conexiones
Sección titulada «Reutilizar el cliente y agrupar las conexiones»RemoteBackend mantiene un httpx.AsyncClient persistente para la agrupación de conexiones. Construir AsyncNextPDF una sola vez, compartirlo entre solicitudes y cerrarlo al apagar el servicio. No crear un cliente por solicitud.
"""Reuse one pooled async client for the lifetime of the process."""
import asyncioimport osfrom 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())El gestor de contexto asíncrono llama a close() al salir, lo que cierra el transporte subyacente. Sin un gestor de contexto, llamar explícitamente a await client.close().
Manejar los errores con la jerarquía de excepciones
Sección titulada «Manejar los errores con la jerarquía de excepciones»El SDK lanza una jerarquía tipada. Todos los errores derivan de NextPDFError; los fallos a nivel de HTTP derivan de NextPDFAPIError y llevan un status_code. Conviene capturar los tipos específicos sobre los que se puede actuar y recurrir al tipo base.
| Excepción | Se lanza cuando | Atributos clave |
|---|---|---|
NextPDFError | Tipo base para todos los errores del SDK | status_code |
NextPDFAPIError | Cualquier error de HTTP del servidor | status_code, error_code |
NextPDFLicenseError | HTTP 402; la función necesita un nivel de servidor superior | status_code (402) |
QuotaExceededError | HTTP 429; límite de tasa o cuota superados | retry_after |
AstNoStructTreeError | HTTP 422; PDF sin etiquetar con el modo heurístico desactivado | status_code (422) |
AstBuildTimeoutError | HTTP 504; la construcción del AST agotó el tiempo de espera | status_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)Reintentar los fallos transitorios con retroceso
Sección titulada «Reintentar los fallos transitorios con retroceso»El SDK no reintenta automáticamente. Envolver las llamadas en un bucle propio que reintente ante fallos de HTTP transitorios y respete el valor Retry-After del servidor, que QuotaExceededError expone como retry_after (un número entero de segundos, o None). Usar retroceso exponencial para otros estados transitorios y no reintentar NextPDFLicenseError.
"""Retry transient failures with exponential backoff and Retry-After support."""
import asynciofrom collections.abc import Awaitable, Callablefrom 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")Gestionar las cuotas, los límites de tasa y los tiempos de espera
Sección titulada «Gestionar las cuotas, los límites de tasa y los tiempos de espera»La aplicación de cuotas y límites de tasa vive en el servidor. Ante un HTTP 429, el SDK lanza QuotaExceededError y analiza el encabezado Retry-After en retry_after. El backend remoto también expone encabezados X-RateLimit-* en las respuestas de render, lo que permite regular el ritmo de forma proactiva antes de alcanzar un límite estricto.
Los tiempos de espera de las solicitudes usan un valor predeterminado fijo de 60 segundos en total con un tiempo de espera de conexión de 10 segundos (httpx.Timeout(60.0, connect=10.0)). Para acotar las construcciones de AST largas, conviene reducir el trabajo con page_range_start, page_range_end o token_budget en lugar de confiar solo en el tiempo de espera; una construcción demasiado larga devuelve AstBuildTimeoutError (HTTP 504).
Arquitecturas de ejemplo
Sección titulada «Arquitecturas de ejemplo»Trabajo por lotes
Sección titulada «Trabajo por lotes»Un worker por lotes lee los PDF, extrae el texto citado y escribe la salida estructurada. Reutiliza un único cliente con grupo de conexiones, acota la concurrencia con un semáforo y aplica la función auxiliar de reintentos anterior.
"""Batch-extract a directory of PDFs over one pooled async client."""
import asyncioimport osfrom 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")))Servicio web
Sección titulada «Servicio web»Un servicio de FastAPI comparte un único AsyncNextPDF entre solicitudes durante el ciclo de vida de la aplicación, de modo que cada solicitud reutiliza el grupo de conexiones. Lee las credenciales desde el entorno y trata la clave de API como un secreto.
"""FastAPI service that shares one pooled NextPDF client across requests."""
import osfrom contextlib import asynccontextmanager
from fastapi import FastAPI, UploadFile
from nextpdf import AsyncNextPDF
@asynccontextmanagerasync 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]}Herramienta de agente
Sección titulada «Herramienta de agente»Para agentes de IA, ejecutar el servidor MCP. Expone herramientas de PDF (por ejemplo, nextpdf_extract_text, nextpdf_extract_tables, nextpdf_get_ast, nextpdf_info, nextpdf_search, nextpdf_get_outline, nextpdf_diff y nextpdf_health) a través de la entrada/salida estándar (input/output). El servidor lee NEXTPDF_BASE_URL y NEXTPDF_API_KEY desde el entorno y, por lo tanto, está respaldado de forma remota; al igual que la CLI, no puede usar el backend local. Instalar el extra opcional y ejecutar el módulo.
pip install "nextpdf[mcp]"python -m nextpdf.mcpConsultar Servidor MCP de Python para la guía de integración de agentes, CLI de Python para el uso desde la terminal y Referencia de la API de Python para conocer la superficie completa de clientes, modelos y excepciones.