Skip to content

LLM Integration

Kreuzberg integrates with 146 LLM providers (including local inference engines) via liter-llm for three capabilities: VLM OCR, structured extraction, and provider-hosted embeddings.

Use vision-language models as an OCR backend. The document page is rendered as an image and sent to the VLM, which returns the extracted text.

  • Low-quality scanned documents where traditional OCR struggles
  • Handwritten text recognition
  • Arabic, Farsi, and other scripts with poor Tesseract/PaddleOCR support
  • Complex layouts where traditional OCR fails (mixed tables, forms, diagrams)
  • When you need higher accuracy and can accept higher latency and API costs
Python
import asyncio
from kreuzberg import extract_file, ExtractionConfig, OcrConfig, LlmConfig
async def main() -> None:
config = ExtractionConfig(
force_ocr=True,
ocr=OcrConfig(
backend="vlm",
vlm_config=LlmConfig(model="openai/gpt-4o-mini"),
),
)
result = await extract_file("scan.pdf", config=config)
print(result.content)
asyncio.run(main())

Override the default prompt template for VLM OCR:

Python
from kreuzberg import ExtractionConfig, OcrConfig, LlmConfig
config = ExtractionConfig(
force_ocr=True,
ocr=OcrConfig(
backend="vlm",
vlm_config=LlmConfig(model="openai/gpt-4o-mini"),
vlm_prompt="Extract all text from this document image. Preserve formatting.",
),
)

Any liter-llm vision-capable provider works as a VLM OCR backend:

Provider Example Model
OpenAI openai/gpt-4o, openai/gpt-4o-mini
Anthropic anthropic/claude-3-5-sonnet-20241022
Google google/gemini-2.0-flash
Groq groq/llama-3.2-90b-vision-preview
Ollama (local) ollama/llama3.2-vision
LM Studio (local) lmstudio/llava-1.5
vLLM (local) vllm/llava-next

Extract structured JSON data from documents by providing a JSON schema. The document is first extracted as text, then sent to an LLM with the schema to produce conforming output.

Python
import asyncio
from kreuzberg import extract_file, ExtractionConfig, StructuredExtractionConfig, LlmConfig
async def main() -> None:
config = ExtractionConfig(
structured_extraction=StructuredExtractionConfig(
schema={
"type": "object",
"properties": {
"title": {"type": "string"},
"authors": {"type": "array", "items": {"type": "string"}},
"date": {"type": "string"},
},
"required": ["title", "authors", "date"],
"additionalProperties": False,
},
llm=LlmConfig(model="openai/gpt-4o-mini"),
strict=True,
),
)
result = await extract_file("paper.pdf", config=config)
print(result.structured_output)
# {"title": "...", "authors": ["..."], "date": "..."}
asyncio.run(main())

Override the default extraction prompt with a Jinja2 template:

Python
from kreuzberg import ExtractionConfig, StructuredExtractionConfig, LlmConfig
config = ExtractionConfig(
structured_extraction=StructuredExtractionConfig(
schema={"type": "object", "properties": {"title": {"type": "string"}}},
llm=LlmConfig(model="openai/gpt-4o-mini"),
prompt=(
"Analyze this document and extract key metadata.\n\n"
"Document:\n{{ content }}\n\n"
"Schema: {{ schema }}"
),
),
)

Available template variables:

Variable Description
{{ content }} The extracted document text
{{ schema }} The JSON schema as a formatted string
{{ schema_name }} The schema name (default: "extraction")
{{ schema_description }} The schema description (may be empty)

Structured extraction handles provider differences automatically:

  • OpenAI: Full strict mode with additionalProperties enforcement
  • Anthropic/Gemini: additionalProperties automatically stripped (not supported by these providers)
  • All providers: Markdown code fence wrapping in responses is automatically handled

When strict=True, the LLM is instructed to produce output that exactly matches the schema. This enables OpenAI’s structured output mode and adds validation on the response.

Use provider-hosted embedding models instead of local ONNX models. Useful when you want to match the embedding model used by your vector database or when local ONNX models are not available.

Python
import asyncio
from kreuzberg import embed, EmbeddingConfig, EmbeddingModelType, LlmConfig
async def main() -> None:
config = EmbeddingConfig(
model=EmbeddingModelType.llm(
LlmConfig(model="openai/text-embedding-3-small")
),
normalize=True,
)
embeddings = await embed(["Hello world"], config=config)
print(len(embeddings[0])) # 1536
asyncio.run(main())
Model Dimensions Provider
openai/text-embedding-3-small 1536 OpenAI
openai/text-embedding-3-large 3072 OpenAI
mistral/mistral-embed 1024 Mistral
Any liter-llm embedding-capable provider Varies Various
v4.8.0

Kreuzberg supports local LLM inference engines via liter-llm’s built-in provider routing. No API key required — just point to your local server.

Engine Prefix Default URL Install
Ollama ollama/ http://localhost:11434/v1 brew install ollama
LM Studio lmstudio/ http://localhost:1234/v1 Desktop app
vLLM vllm/ http://localhost:8000/v1 pip install vllm
llama.cpp llamacpp/ http://localhost:8080/v1 Build from source
LocalAI localai/ http://localhost:8080/v1 Docker
llamafile llamafile/ http://localhost:8080/v1 Single binary
Terminal window
# Start Ollama and pull a model
ollama pull llama3.2-vision
# Use it for VLM OCR (no API key needed)
kreuzberg extract scan.pdf --force-ocr true \
--vlm-model ollama/llama3.2-vision
# Use it for structured extraction
kreuzberg extract-structured doc.pdf \
--schema schema.json \
--model ollama/llama3.2
# Use it for embeddings
kreuzberg embed --provider llm \
--model ollama/all-minilm \
--text "Hello world"

Every LLM call made during extraction is tracked in the llm_usage field of ExtractionResult. Each entry records the model used, token counts, estimated cost, and why the model stopped generating.

result = await extract_file("document.pdf", config)
if result.get("llm_usage"):
for usage in result["llm_usage"]:
print(f"{usage['source']}: {usage['input_tokens']} in, {usage['output_tokens']} out, ${usage['estimated_cost']:.4f}")

The source field indicates which pipeline stage triggered the call: "vlm_ocr", "structured_extraction", or "embeddings".

API keys can be set via (in order of precedence):

  1. api_key field in LlmConfig — highest priority, per-request
  2. Provider standard env vars (OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY, etc.)
  3. Kreuzberg-specific env var (KREUZBERG_LLM_API_KEY) — used as fallback for any provider
Python
from kreuzberg import LlmConfig
# Explicit API key
config = LlmConfig(model="openai/gpt-4o", api_key="sk-...")
# Custom base URL (e.g., Azure OpenAI, local proxy)
config = LlmConfig(
model="openai/gpt-4o",
base_url="https://my-proxy.example.com/v1",
)
Field Type Default Description
model str required Provider/model in liter-llm format (for example, "openai/gpt-4o")
api_key str | None None API key (falls back to env vars)
base_url str | None None Custom endpoint URL
timeout_secs int | None 60 Request timeout in seconds
max_retries int | None 3 Maximum retry attempts
temperature float | None None Sampling temperature
max_tokens int | None None Maximum tokens to generate

POST /extract-structured — multipart form with file + schema + model configuration.

Terminal
curl -X POST http://localhost:4000/extract-structured \
-F "file=@invoice.pdf" \
-F 'schema={"type":"object","properties":{"vendor":{"type":"string"},"total":{"type":"number"}}}' \
-F "model=openai/gpt-4o-mini" \
-F "strict=true"

When running Kreuzberg as an MCP server, LLM features are available as tools:

  • extract_structured — extract structured data from a document using a JSON schema
  • embed_text — extended with model parameter for LLM-hosted embeddings