Use case
Data extraction
Pull fields out of invoices, receipts, emails and screenshots into JSON your code can trust. The model does the reading; a schema check in your code decides whether the result is accepted.
The problem
Documents arrive in every layout imaginable, and rule-based parsers break on the first new template. Models read them well but occasionally return a malformed date, a missing field or a total that doesn't add up. Extraction is only useful if bad output is caught before it reaches your database.
How it works with NeuronGate
01
Document
Text, or an image of the page
02
Extraction prompt
Field list and an example JSON
03
Model reads
Vision models take images directly
04
Schema check
Pydantic or Zod in your code
05
Accept or retry
Retry with the error, then flag for review
Vision-capable models accept images as image_url content parts, including base64 data URLs, so a scanned invoice can go straight to the model. Use the catalog's vision filter to see which models take images.
Choosing models
| Role | Good fit | Why | Trade-off |
|---|---|---|---|
| Scans and screenshots | ~google/gemini-flash-latest | Reads images well at a low price per page | Verify small print and handwritten fields |
| Plain-text documents | deepseek/deepseek-v4-flash | Cheap for emails and text-layer PDFs | Text only: no images |
| Retries and hard cases | ~anthropic/claude-sonnet-latest | More reliable on messy layouts and tables | Use it for the failures, not every page |
A common pattern: run everything through the cheap model, and send only documents that fail validation twice to the stronger one. Current prices are in the model catalog.
Example: an invoice from an image
The schema is the contract. The model is told the fields; your code checks them and retries once with the validation error so the model can correct itself.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://neurongate.net/v1",
api_key=os.environ["NEURONGATE_API_KEY"],
)
import base64, json
from datetime import date
from pydantic import BaseModel, ValidationError
class Invoice(BaseModel):
vendor: str
invoice_number: str
issue_date: date
currency: str
total: float
PROMPT = (
"Extract the invoice as JSON with keys vendor, invoice_number, "
"issue_date (YYYY-MM-DD), currency (ISO code) and total (number). "
"Reply with the JSON object only."
)
def extract(image_path: str) -> Invoice:
b64 = base64.b64encode(open(image_path, "rb").read()).decode()
messages = [{"role": "user", "content": [
{"type": "text", "text": PROMPT},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
]}]
for attempt in range(2):
reply = client.chat.completions.create(
model="~google/gemini-flash-latest", messages=messages, temperature=0,
)
raw = reply.choices[0].message.content or ""
try:
text = raw.strip().removeprefix("```json").removesuffix("```")
return Invoice.model_validate(json.loads(text))
except (json.JSONDecodeError, ValidationError) as err:
messages += [
{"role": "assistant", "content": raw},
{"role": "user", "content": f"That was invalid: {err}. Reply with corrected JSON only."},
]
raise ValueError(f"Could not extract {image_path}; flag for review")Validate meaning, not just shape
Images cost tokens too
Cost control
- 1
Give the workload its own key
Create a key just for the extraction pipeline under API keys. Its usage then shows up separately in the logs, and revoking it touches nothing else. - 2
Set a monthly spend cap
Once the key has spent its cap for the month, its requests are refused until the next month or until you raise the cap. Size it from a week of real traffic, not a guess. - 3
Allowlist the models it may call
Restrict the key to the models this workload actually uses, for example~google/gemini-flash-latestand~anthropic/claude-sonnet-latest. A typo or a prompt-injected model switch then fails instead of quietly running on a pricier model. - 4
Watch the logs
Filter the request log by this key to see tokens, cost and latency per call. Export CSV for a monthly cost-per-workload report.
FAQ
Are the documents I send stored?
No. Request and response content passes through and isn't stored; NeuronGate keeps usage metadata only (model, tokens, cost, latency, status).
How do I send a PDF?
Either extract the text layer and send it as text, or render each page to an image and send it to a vision-capable model as an image_url content part.
Why validate in my code instead of trusting the model?
Even good models occasionally return a wrong type or a missing field. A schema check plus one retry with the error message fixes most of these, and anything still invalid is flagged instead of loaded.