Metadata-Version: 2.5
Name: langchain-velrim
Version: 0.1.0
Summary: LangChain document loader and agent tool for the Velrim document-extraction API: extraction against a JSON Schema you supply, with per-field state (present, null, missing), a per-field confidence score, and source anchors (page + bounding box).
Project-URL: Homepage, https://velrim.com
Author-email: Velrim <hello@velrim.com>
License-Expression: MIT
License-File: LICENSE
Keywords: document extraction,document loader,json schema,langchain,velrim
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: langchain-core<2,>=0.3
Requires-Dist: velrim<0.3,>=0.2.1
Description-Content-Type: text/markdown

# langchain-velrim

LangChain integration for the [Velrim](https://velrim.com) document-extraction API.

Velrim does document extraction against a JSON Schema you supply. Every schema leaf comes back
with a per-field state (present / null / missing), a per-field confidence score, and source
anchors (page + bounding box). This package ships two pieces:

- `VelrimLoader`, a `BaseLoader` that turns each input document into one LangChain `Document`
  whose `page_content` is the extracted object and whose metadata carries the per-field detail.
- `VelrimExtractTool`, a `BaseTool` an agent can call. The model passes a document reference;
  your resolver turns it into bytes. The model never sees the document bytes.

Requires Python 3.9+, `langchain-core>=0.3,<2`, and the `velrim` SDK (installed with it).

## Install

```bash
pip install langchain-velrim
# or
uv add langchain-velrim
```

Set `VELRIM_API_KEY` in the environment, or pass `api_key=...` / a `velrim.Client`.

## Loader

```python
from pydantic import BaseModel
from langchain_velrim import VelrimLoader


class Invoice(BaseModel):
    invoice_number: str
    total: float


loader = VelrimLoader(
    ["invoices/2026-001.pdf", "invoices/2026-002.pdf"],
    schema=Invoice,  # a Pydantic model class or a JSON Schema dict
    doc_class="invoice",  # optional hint
    confidence_threshold=0.8,  # optional; feeds velrim_review
)

for doc in loader.lazy_load():  # load() and aload() also work
    print(doc.page_content)  # json.dumps(data, indent=2)
    print(doc.metadata["velrim_review"])
```

Each entry in the list is a file path (`str` or `os.PathLike`), raw `bytes`, or a
`velrim.Document`. Use `velrim.Document.from_upload_key(...)` for a document staged through the
upload API. One input document produces one output `Document`.

Pass your own client when you need a custom base URL, timeout, or retry count:

```python
from velrim import Client

client = Client(api_key="...", timeout=120)
loader = VelrimLoader([pdf_bytes], schema=Invoice, client=client)
```

### Metadata

| Key                         | Type        | Meaning                                          |
| --------------------------- | ----------- | ------------------------------------------------ |
| `source`                    | `str`       | The file path, the upload key, or `"bytes"`      |
| `velrim_request_id`         | `str`       | Request id for support and log correlation       |
| `velrim_pages`              | `int`       | Pages in the document                            |
| `velrim_billed_pages`       | `int`       | Pages billed                                     |
| `velrim_model`              | `str`       | Model that produced the extraction               |
| `velrim_calibrator_version` | `str`       | Version of the confidence calibrator             |
| `velrim_doc_class`          | `str`       | Only present when `doc_class` was set            |
| `velrim_review`             | `list[str]` | JSON Pointers that need human review (see below) |
| `velrim_fields`             | `dict`      | The full per-field map, keyed by JSON Pointer    |

`velrim_review` lists every leaf whose state is `missing`, every leaf where the extraction passes
disagreed (`conflict: true`), and, when `confidence_threshold` is set, every leaf whose confidence
is below it. Without a threshold only the first two rules apply.

`velrim_fields` maps each JSON Pointer (`"/total"`, `"/line_items/0/sku"`) to its state, value,
confidence, anchor (page, bounding box, snippet, page dimensions), conflict flag, and reason.

### Flat metadata for vector stores

`velrim_fields` is a nested object. Vector stores that only accept scalar metadata (many do)
reject it, so turn it off for those and keep the flat keys plus `velrim_review`:

```python
loader = VelrimLoader(paths, schema=Invoice, include_fields=False)
```

`include_fields` defaults to `True`.

## Agent tool

```python
from pathlib import Path

from velrim import Client
from langchain_velrim import VelrimExtractTool

client = Client()  # reads VELRIM_API_KEY


def resolve_document(ref: str) -> bytes:
    # The model sends a reference; you decide what it means (a path, an S3 key, a row id).
    return Path("inbox", ref).read_bytes()


extract = VelrimExtractTool(
    client=client,
    schema=Invoice,
    resolve_document=resolve_document,
    confidence_threshold=0.8,  # optional
    doc_class="invoice",  # optional default; the model may override per call
)

# Bind it like any other LangChain tool, for example:
# agent = create_agent(model, tools=[extract])
```

The tool is named `velrim_extract`. Its input schema is `{document: str, doc_class?: str}`.
The resolver returns raw `bytes` or a `velrim.Document` (so `Document.from_upload_key(...)` works
for large documents). The result is a JSON-serializable dict:

```json
{
  "data": { "invoice_number": "INV-1001", "total": 4200.0 },
  "review": ["/total"],
  "request_id": "req_...",
  "pages": 1
}
```

`review` follows the same rule as `velrim_review` above.

## Errors

Errors from the `velrim` SDK propagate unchanged, so you can catch the typed classes
(`velrim.InsufficientBalanceError`, `velrim.RateLimitedError`, `velrim.APIError`, ...) around
`load()` or the tool call. Neither the loader nor the tool swallows them.

## License

MIT.
