Documents from a template with AI: filling templates from data

A Word template, a data table and a script: hundreds of standard documents in one run. What to give the model, what to compute in code and how to verify fields.

4 min · · автоматизация · бизнес

An AI model for generating documents from a template is useful where you have dozens of standard papers: contracts, acts, powers of attorney, letters, certificates. There is one template and the data changes. The main trap is that many people ask the model to "write the whole document from the sample" and get quietly altered details and amounts. The right scheme is different: the template stays rigid, data comes from a table, and the model is responsible only for what needs understanding of Russian. Below is such a scheme in Python.

AI document generation from a template: what code does and what the model does

What the document needsWho does it
Details, numbers, dates, amountsCode, from the table unchanged
Amount in words, VAT, totalsCode (calculation and a library)
Name and position in the genitive case, "represented by ... acting on the basis of ..."Model
Description of a service from a manager's short noteModel, then checked by a person
Structure and legal wordingThe template, written by a lawyer

The legal text in the template should be written not by the model but by your lawyer. The model is needed here only for declensions and business wording; everything else stays with the template and the code.

Step 1. Prepare the Word template

Take a ready document sample and replace the variable parts with tags. For Python the docxtpl library (Jinja tags in a Word document) is convenient:

bash
CONTRACT No. {{ number }} dated {{ date }}
{{ client_org }}, hereinafter the "Customer", represented by {{ signer_genitive }},
acting on the basis of {{ basis_genitive }}, ...
The cost of the services is {{ amount }} RUB ({{ amount_words }}).

An important detail: type the tag in Word in one go, without changing formatting inside it. Otherwise breaks appear inside the tag and substitution fails. If a tag does not work, retype it in one pass or paste it as plain text.

Step 2. Data in a table

Keep "raw" data in CSV or Excel (read with csv or openpyxl): number, date, client_org, inn, signer (nominative), position, basis ("Charter"), amount, service_note. Each row is one future document.

Step 3. The model prepares the text fields

The model receives the name, position and basis and returns strict JSON. Use structured output as in the article how to get strictly structured JSON.

bash
import csv
import json
import os
from decimal import Decimal
from docxtpl import DocxTemplate
from num2words import num2words
from openai import OpenAI

client = OpenAI(base_url="https://api.zukko.pro/v1",
                api_key=os.environ["ZUKKO_API_KEY"])

SYSTEM = (
    "You are an editor of business documents. You get a signatory's data. "
    "Return JSON with fields: signer_genitive (position and full name in the "

A cheap model is enough for declensions, but surname declension can be tricky (foreign, indeclinable and female surnames), so spot checks are mandatory. More on calls from Python is in the Python reference.

Step 4. Numbers and checks are computed by code

bash
def money_fields(row):
    amount = Decimal(row["amount"].replace(" ", "").replace(",", "."))
    rub = int(amount)
    kop = int((amount - rub) * 100)
    words = num2words(rub, lang="ru")
    return {"amount": f"{amount:,.2f}".replace(",", " ").replace(".", ","),
            "amount_words": f"{words} руб. {kop:02d} коп."}

def validate(row, ctx):
    errors = []
    inn = row["inn"].strip()
    if not (inn.isdigit() and len(inn) in (10, 12)):
        errors.append("INN: 10 or 12 digits required")
    for key in ("number", "date", "client_org", "signer_genitive"):
        if not ctx.get(key):
            errors.append(f"empty field {key}")
    return errors

For the amount in words use a library, not the model: the model may distort the words for a sum while a library is deterministic. Test it on your own values, especially kopecks and rubles in different cases, and adjust the output if needed. The INN checksum can also be verified in code if you need strict validation.

Step 5. Building documents and the final check

bash
with open("clients.csv", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        ctx = {**row, **text_fields(row), **money_fields(row)}
        problems = validate(row, ctx)
        if problems:
            print(row["number"], "skipped:", problems)
            continue
        doc = DocxTemplate("template.docx")
        doc.render(ctx)
        doc.save(f"out/dogovor_{row['number']}.docx")

After building, open the output file with the same code and make sure no {{, None or blanks remain; read a random sample of 5-10 documents in full. Compare not only fields but also the numeric amount against the amount in words. If you need to extract data from received scans or texts, see the article extracting data from documents.

What about personal data

Signatories' surnames and details are data. Decide in advance what you may send to an external service. The minimum option: only the fields for declension (position, name, basis) go to the model, while INN, amounts and addresses stay with you. Read the data retention terms of model providers separately; we make no claims about them.

What it costs

Formula: (input × input price + output × output price) / 1,000,000, with prices per 1M tokens as of October 18, 2026. Assumptions: 700 input and 300 output tokens per document, 200 documents in total, that is 140,000 input and 60,000 output tokens. This is an upper estimate because we send only short fields.

  • deepseek-v4.1-flash: 140,000 × 0.013 / 1,000,000 + 60,000 × 0.038 / 1,000,000 = 0.00182 + 0.00228, about $0.004.
  • gpt-6.1-sol: 140,000 × 0.27 / 1,000,000 + 60,000 × 1.32 / 1,000,000 = 0.0378 + 0.0792, about $0.12.
  • claude-sonnet-5-5: 0.056 + 0.12, about $0.18.

Test several models on 30 hard surnames and choose the cheapest one that does not err. Current prices: GET https://api.zukko.pro/v1/models.

Connecting

Create a key in the dashboard, store it in the ZUKKO_API_KEY variable and set the base URL https://api.zukko.pro/v1. The balance is topped up from $1: SBP (the payment page commission is 8.5% on top), cryptocurrency or a MasterCard.

FAQ

Can I just upload a sample and ask the AI to make the same document?

For a draft, yes, but details and amounts are easily distorted that way. For standard documents a rigid template with substitution is more reliable.

Does the scheme fit filling PDF forms?

The principle is the same: code and the model prepare the fields, while filling depends on the form format. For PDF you usually need a separate library for forms.

How do I check declensions?

Make a test set of 30 hard surnames and positions, compare the output with a reference and keep it for re-checks when you change the model.

Who is responsible for the legal force of a document?

You are. The model gives no legal opinions, and a lawyer checks the final text.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

AI document generation from a template: Python guide · Zukko