Skip to main content

fintom8

LiteLLM connector for Gemini / Vertex AI / OpenAI / Azure. Chat, stream, and document extract. Students install with pip and call a few methods — keys stay in .env.

Requires Python 3.14+.

Install from Git (recommended for private use)

GitHub Packages Python upload is currently unreliable (SSL issues). Install directly from this repo instead:

# HTTPS (use a PAT with repo read access if the repo is private)
pip install "git+https://github.com/NikolaienkoIgor/f8_templates.git#subdirectory=fintom8"

# Pin a tag / commit
pip install "git+https://github.com/NikolaienkoIgor/f8_templates.git@fintom8-v0.1.8#subdirectory=fintom8"

# SSH (no token in the URL if your SSH key is set up)
pip install "git+ssh://git@github.com/NikolaienkoIgor/f8_templates.git#subdirectory=fintom8"

Private HTTPS with an explicit token:

pip install "git+https://<GITHUB_USERNAME>:<GITHUB_PAT>@github.com/NikolaienkoIgor/f8_templates.git#subdirectory=fintom8"

Other install options

# Public PyPI (when published)
pip install fintom8

# Local editable install from this checkout
pip install -e ./fintom8
# or: pip install -e "./fintom8[dev]"
from fintom8 import LLM

llm = LLM()  # reads .env / environment
print(llm.chat("Summarize this invoice").text)

Configuration

Resolution order: constructor kwargs / LLMConfig > environment > defaults.

Copy .env.example to .env in your project (never commit it).

Param Env Default When needed
model LLM_MODEL gemini/gemini-3.5-flash always
temperature LLM_TEMPERATURE unset (Gemini 3+), 1.0 (older Gemini/Vertex), else 0.0 optional; omitted for Gemini 3+ (deprecated by Google)
num_retries 3 optional
api_key GEMINI_API_KEY / OPENAI_API_KEY / AZURE_API_KEY (from model prefix) unset Gemini / OpenAI / Azure (azure/ → required)
api_base AZURE_API_BASE / OPENAI_API_BASE unset Azure (azure/ → required)
api_version AZURE_API_VERSION unset Azure (azure/ → required)
vertex_project VERTEXAI_PROJECT unset Vertex
vertex_location VERTEXAI_LOCATION eu Vertex

For azure/<deployment>, missing api_key, api_base, or api_version raises Fintom8Error (set via constructor or AZURE_* env vars).

from fintom8 import LLM, LLMConfig

llm = LLM()  # env defaults
llm = LLM(model="gpt-4o", api_key="sk-...", temperature=0)
llm = LLM(LLMConfig(
    model="azure/my-deploy",
    api_key="...",
    api_base="https://....openai.azure.com",
    api_version="2024-10-21",
))

Switch provider

LLM_MODEL Env
gemini/gemini-3.5-flash GEMINI_API_KEY
vertex_ai/gemini-3.5-flash VERTEXAI_PROJECT + VERTEXAI_LOCATION + ADC (gcloud auth application-default login)
gpt-4o OPENAI_API_KEY
azure/<deployment> AZURE_API_KEY + AZURE_API_BASE + AZURE_API_VERSION

Contributors

Usage

from fintom8 import LLM

llm = LLM()

resp = llm.chat("Hello")
print(resp.text, resp.usage)

# Structured output — file only; detect format → LiteLLM SO → cleanse dates
invoice_rf = {
    "type": "json_schema",
    "json_schema": {
        "name": "Invoice",
        "strict": True,
        "schema": {
            "type": "object",
            "properties": {
                "total": {"type": "number"},
                "vendor": {"type": "string"},
                "invoiceDate": {"type": ["date", "null"]},
            },
            "required": ["total", "vendor", "invoiceDate"],
            "additionalProperties": False,
        },
    },
}
data = llm.structured(
    "invoice.pdf",  # also: .png/.jpg, .txt/.csv/.xml/.xlsx, or bytes
    structuredOutput=invoice_rf,
    dateFormat="DD.MM.YYYY",
)
# {"total": 42.5, "vendor": "Acme", "invoiceDate": "08.08.2026"}

# Invoice / EN16931 / chem ping-pong (keyword args)
report = llm.generate_and_validate(name="invoice", source="invoice.pdf")
# {"correct": true, "name": "invoice", "format": "json", "artifact": {…}, …}

en16931 = llm.generate_and_validate(
    name="en16931", source="invoice.pdf", invoice_format="ubl"
)
# en16931["artifact"] is the last UBL XML string; requires fintom8[schematron]

chem = llm.generate_and_validate(name="chemical_composition", source="cert.pdf")
# Prefer a path for source; pass filePath= only when source is raw bytes

# Document → UBL / ZUGFeRD XML (LLM generate only; no Schematron loop)
ubl_xml = llm.convert("invoice.pdf", invoice_format="ubl")
zugferd_xml = llm.convert("invoice.csv", invoice_format="zugferd")
# also: .json / .xml / images / .xlsx — or bytes with mime= / filePath=

# process() is an alias of structured()
data = llm.process(
    "invoice.pdf",
    structuredOutput=invoice_rf,
    dateFormat="DD.MM.YYYY",
    instructions="Extract invoice vendor and total.",
)

for chunk in llm.stream([{"role": "user", "content": "Write a haiku"}]):
    print(chunk, end="", flush=True)

resp = llm.extract("invoice.pdf", response_format=invoice_rf)

Async twins: achat, astream, aextract, astructured, aprocess, aconvert, agenerate_and_validate, acompare.

Optional helpers: detect_format, fields_to_schema, compile_fields, prepare_response_format, apply_cleanse, json_schema_response_format, structured_output, enforce_strict, inline_refs, compare, acompare.

Compare two sources

LLM-based dual-source comparison with built-in profiles or a custom schema:

from fintom8 import LLM, compare

llm = LLM()

# PDF/image/document vs invoice XML (payment-critical mismatches)
report = llm.compare("invoice.pdf", "invoice.xml", profile="document_xml")
# {"matches": true, "total_mismatches": 0, "mismatches": []}

# Mapping document vs reference rules text (Peppol gap analysis step 1)
findings = llm.compare(
    "mapping.html",
    "PEPPOL-EN16931-UBL.sch",
    profile="mapping_reference",
    max_findings=5,
    model="gemini/gemini-3.1-pro-preview",
)

# Custom comparison
custom = llm.compare(
    doc_a,
    doc_b,
    profile="custom",
    instructions="Compare these two contracts for clause differences.",
    structuredOutput={"differences": {"type": "array", "items": {"type": "string"}}},
)

Register additional profiles with fintom8.comparison.register_compare_profile(). See examples/compare_document_xml.py.

Bundled templates

from fintom8.templates import invoice
# or: from fintom8 import templates; templates.invoice
# or: from fintom8 import use_template; use_template("invoice")

data = llm.structured(
    "invoice.pdf",
    structuredOutput=invoice["structuredOutput"],
    systemPrompt=invoice["systemPrompt"],
    dateFormat=invoice.get("dateFormat", "YYYY-MM-DD"),
)

list_templates() lists packaged names (invoice, chemical_composition, recipient_statement). Pass a path or dict to use_template for custom templates.

Validation

One engine, one envelope. Document types differ only by the pack registered for that name. ERP check is a second step on the same envelope — not a different API.

from fintom8 import validate, erp_check

report = validate("invoice", payload)
report = validate("chemical_composition", payload)
report = validate("recipient_statement", payload)

# EN16931 Schematron on UBL or CII XML (format is detected; requires fintom8[schematron])
en16931 = validate("en16931", {"xml": ubl_or_cii_xml})
# or: validate("en16931", {"xml_path": "invoice.xml"})

# ERP check is named + injected. qc55 is SAP vs chemical coils only.
qc55 = erp_check("qc55", chem_payload, reference=qc55_rows)  # bundled qc55.csv if omitted

Every call returns:

{
  "correct": false,
  "name": "invoice",
  "format": "json",
  "errors": [{"path": "lineItems[0].totalPriceWithTax", "message": "...", "code": "FORMULA"}],
  "warnings": [],
  "artifact": {},
  "units": null,
  "_debug": null
}

artifact is the document (invoice JSON, chemical extraction, recipient JSON, or EN16931 XML string). format is json, ubl, or cii. Chemical and QC55 put per-coil reports in units (including a single coil). Invoice rewritten totals live in artifact (the caller's payload is not mutated). QC55 match rows live in units[coil_id].artifact. EN16931 format is ubl or cii; Schematron rule ids (e.g. BR-CO-15) are in errors[].code and XPath in errors[].path. _debug is {attempts, exhausted, raw} only when include_debug=True.

Install Schematron support with:

pip install "fintom8[schematron]"

PDF/A-3 (embed invoice XML into a PDF) is available via:

pip install "fintom8[pdf]"
from fintom8 import create_pdfa3, extract_xml

pdfa3_bytes = create_pdfa3("invoice.pdf", "invoice.xml")
xml = extract_xml(pdfa3_bytes)  # or extract_xml("invoice_zugferd.pdf")

Bundled EN16931 XSLT files are licensed under EUPL 1.2 (CEN). Default pip install fintom8 does not require Saxon or pikepdf.

Chemical composition document validation does not call QC55; sequence them explicitly:

from fintom8 import validate, erp_check

chem = validate("chemical_composition", extracted)
qc55 = erp_check("qc55", extracted)  # or erp_check("qc55", extracted, reference=live_rows)

Optional aliases: validate_invoice, validate_recipient_statement, validate_en16931, erp_check_qc55 — same envelope as the named calls above.

Generate + validate (ping-pong)

One standard wrapper. name selects SO (JSON schema) or NON-SO (XML); source is the document path or bytes. Same Validation Wrapper loop: validate → on error, correction prompt + regenerate → until correct or max attempts.

from fintom8 import LLM

llm = LLM()
report = llm.generate_and_validate(name="invoice", source="invoice.pdf", include_debug=True)

report = llm.generate_and_validate(
    name="en16931", source="invoice.pdf", invoice_format="ubl", include_debug=True
)
# report["artifact"], report["_debug"]["attempts"]

report = llm.generate_and_validate(name="chemical_composition", source="cert.pdf")

Packs stay pure (no LLM/retry inside validation/). Pass filePath= / mime= only when source is raw bytes.

Convert document → UBL / ZUGFeRD XML (generate only)

Same idea as the platform invoice-agent generate step without the Schematron loop. For generate + validate + fix, use generate_and_validate(name="en16931", source=…) instead.

from fintom8 import LLM, convert_to_xml

llm = LLM()
ubl = llm.convert("invoice.pdf", invoice_format="ubl")
cii = llm.convert("invoice.json", invoice_format="zugferd")  # aliases: cii, factur-x
# or: convert_to_xml(llm, "invoice.pdf", invoice_format="ubl")

See examples/convert_to_xml.py, examples/validate_en16931.py.

See examples/invoice_extraction_fintom8.py, examples/generate_and_validate.py, examples/validate_invoice.py, examples/validate_chemical_composition.py, examples/validate_recipient_statement.py, examples/validate_en16931.py, examples/erp_check_qc55.py.

Also exported: Invoice, LineItem, ValidationReport, FieldError, validation_payload_from_llm. Packs are pure (dict in, report out) — no LLM, HTTP, or retry loops.

Failures raise Fintom8Error.

If you see an authentication error (for example missing GEMINI_API_KEY, OPENAI_API_KEY, or Vertex setup), that means package import and retries are working; configure credentials for the selected LLM_MODEL.

See examples/chat.py and examples/extract.py.

Publish (maintainers)

  1. Install dev extras and run tests:

    cd fintom8
    pip install -e ".[dev]"
    pytest
    python -c "from fintom8 import LLM"
    
  2. Build:

    python -m build
    
  3. Upload to TestPyPI first, then PyPI:

    python -m twine upload --repository testpypi dist/*
    python -m twine upload dist/*
    
  4. Tag for CI Trusted Publishing (OIDC). Create the PyPI project once and add a GitHub environment pypi with Trusted Publisher pointing at .github/workflows/publish-fintom8.yml. Then:

    git tag fintom8-v0.1.8
    git push origin fintom8-v0.1.8
    

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fintom8-0.1.8.tar.gz (230.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fintom8-0.1.8-py3-none-any.whl (241.8 kB view details)

Uploaded Python 3

File details

Details for the file fintom8-0.1.8.tar.gz.

File metadata

  • Download URL: fintom8-0.1.8.tar.gz
  • Upload date:
  • Size: 230.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.7

File hashes

Hashes for fintom8-0.1.8.tar.gz
Algorithm Hash digest
SHA256 063adf4f21e7a03297dde185428f9ea7277ae4c5a2f929f6e2c67b47f571174b
MD5 97ec23758361c2b0b566871f40523a82
BLAKE2b-256 7e044551a7284d616745682a980f8f79e345a2da0fdf4f7848c6d24a28573688

See more details on using hashes here.

File details

Details for the file fintom8-0.1.8-py3-none-any.whl.

File metadata

  • Download URL: fintom8-0.1.8-py3-none-any.whl
  • Upload date:
  • Size: 241.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.7

File hashes

Hashes for fintom8-0.1.8-py3-none-any.whl
Algorithm Hash digest
SHA256 dadaceb75d3c8e4de81df0fcb53aa8b2560db85b3da22ef1f758817071d0523b
MD5 bd5188b4f441ac52928c22a2e3bd052f
BLAKE2b-256 34f5a846db6dc80aa17c1699826776f20dc63a0e034e8a4681a23a0dc5a1b17c

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.9

2 files

This release

0.1.8 This release

2 files

0.1.7

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page