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 needs | Who does it |
|---|---|
| Details, numbers, dates, amounts | Code, from the table unchanged |
| Amount in words, VAT, totals | Code (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 note | Model, then checked by a person |
| Structure and legal wording | The 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:
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.
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
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 errorsFor 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
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.
Related articles
- AI for testers: test cases, checklists and bug reportsHow a QA engineer can use a model for test cases, checklists and bug reports, check coverage, and avoid requirements the model made up.
- Anonymizing data before sending it to a neural network: a practical wayYou do not have to send personal data to a model for it to process a document. How to replace it with labels, restore it in the answer and check that nothing leaked.
- A neural network for HR: job posts, candidate emails and resume reviewA model can draft job posts and emails and do a first-pass resume review. What to hand over, how to treat candidates' personal data and how to prevent biased decisions.
- Generating an FAQ with a neural network from tickets and a knowledge baseA good FAQ is built on what customers really ask. A pipeline: group the tickets, answer from the knowledge base with a source reference, and verify the result.