LibreOffice + AI: Building Simple Add-ons to Bring LLM Features to Offline Word Processing
AIproductivityopen-source

LibreOffice + AI: Building Simple Add-ons to Bring LLM Features to Offline Word Processing

UUnknown
2026-03-02
10 min read
Advertisement

Build a LibreOffice add-on that uses local LLMs for offline summarization, grammar edits, and citation suggestions — step-by-step for students.

Bring private LLM features into LibreOffice — even offline

Frustrated by cloud-only AI assistants and worried about leaking your drafts? You're not alone. Students and researchers need summarization, grammar fixes, and citation help — but many don't want drafts sent to corporate servers. This tutorial shows, step-by-step, how to build simple LibreOffice add-ons that connect to local or privacy-preserving LLMs for summarization, grammar editing, and citation suggestions in 2026.

We start with the most important part: a working, minimal extension you can install today. Then we explain how to connect it to different local LLM setups (quantized ggml models, OpenAI-compatible local servers, or secure LAN inference), package the extension as a .oxt, and add toolbar buttons. No cloud required — and designed for students who want practical, repeatable results.

Why this matters in 2026

In late 2025 and early 2026 the landscape shifted: open LLMs became faster in low-memory environments, quantized 4-bit models and efficient inference runtimes (ggml/llama.cpp derivatives, vLLM-like optimizations, and OpenAI-compatible local servers) made on-device AI realistic for many laptops. At the same time, universities and privacy-conscious teams increasingly require offline tools for graded work and sensitive notes.

Key 2026 trends:

  • Open models and better quantization make local LLMs accurate and small.
  • Standardized, OpenAI-compatible local inference APIs are common (text and chat endpoints).
  • Tooling to run local inference in containers or simple CLI servers (e.g., Ollama-style, text-generation-webui forks, llama-cpp-python backends) is widely available.

What you’ll build (quick overview)

By the end of this tutorial you'll have:

  • A tiny LibreOffice Python extension that adds three commands: Summarize selection, Improve grammar, and Suggest citations.
  • Code to call a local LLM using either an OpenAI-compatible HTTP API or a lightweight requests/urllib call to a local server.
  • A packaging guide to produce a .oxt you can install in LibreOffice.
  • Notes on privacy, citation verification, and production readiness.

Prerequisites

  • LibreOffice 7.5+ (8.x recommended). Python macro support enabled (LibreOffice ships with embedded Python).
  • A local LLM inference endpoint: options include llama.cpp / ggml backends, GPT4All, or an OpenAI-compatible local server (text-generation-inference, Ollama-style, or a container running a 2026 open model). For students, running a small quantized model is often enough.
  • Basic familiarity with Python and the UNO API (we provide the exact snippets).

Project structure (minimal)

Create a working folder named libreai-addon with this layout:

libreai-addon/
  ├─ META-INF/
  │   └─ manifest.xml
  ├─ description.xml
  ├─ python/
  │   └─ libreai.py
  └─ Addons.xcu  (optional: toolbar/menu registrations)
  
  Zip that into libreai-addon.oxt to install.

The core LibreOffice Python script

Place this file at python/libreai.py. It uses the UNO XSCRIPTCONTEXT that's available to LibreOffice Python macros. The sample supports calling a local OpenAI-compatible chat endpoint (common in 2026) and a fallback that posts raw prompts to a /completions endpoint.

import json
import os
try:
    import requests
except Exception:
    requests = None

from com.sun.star.task import ErrorCodeIOException

# XSCRIPTCONTEXT is injected by LibreOffice when running Python macros

API_URL = os.environ.get('LOCAL_LLM_API', 'http://127.0.0.1:8000/v1/chat/completions')
API_KEY = os.environ.get('LOCAL_LLM_KEY', '')  # optional

HEADERS = {'Content-Type': 'application/json'}
if API_KEY:
    HEADERS['Authorization'] = f'Bearer {API_KEY}'


def _get_selected_text():
    doc = XSCRIPTCONTEXT.getDocument()
    controller = doc.getCurrentController()
    sel = controller.getSelection()
    try:
        text = sel.getString()
    except Exception:
        # fallback for multiple ranges
        text = ''
        for i in range(sel.getCount()):
            rng = sel.getByIndex(i)
            text += rng.getString() + '\n'
    return sel, text


def _replace_selection(selection, new_text):
    try:
        selection.setString(new_text)
    except Exception as e:
        raise ErrorCodeIOException(str(e))


def _call_local_llm_chat(system_prompt, user_prompt):
    payload = {
        'model': 'local-2026',
        'messages': [
            {'role': 'system', 'content': system_prompt},
            {'role': 'user', 'content': user_prompt}
        ],
        'max_tokens': 512
    }
    if requests:
        r = requests.post(API_URL, headers=HEADERS, json=payload, timeout=30)
        r.raise_for_status()
        data = r.json()
        # OpenAI-compatible: data['choices'][0]['message']['content']
        try:
            return data['choices'][0]['message']['content']
        except Exception:
            # fallback for other servers
            return data.get('text', '')
    else:
        # Minimal urllib fallback
        import urllib.request
        req = urllib.request.Request(API_URL, data=json.dumps(payload).encode(), headers=HEADERS)
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = json.loads(resp.read().decode())
            return data['choices'][0]['message']['content']


def summarize_selection():
    selection, text = _get_selected_text()
    if not text.strip():
        return
    system = 'You are a concise academic summarizer. Produce a 3-sentence summary, preserving key facts.'
    result = _call_local_llm_chat(system, text)
    _replace_selection(selection, result)


def grammar_fix_selection():
    selection, text = _get_selected_text()
    if not text.strip():
        return
    system = 'You are a professional editor. Improve grammar and style but preserve meaning. Show only the corrected text.'
    result = _call_local_llm_chat(system, text)
    _replace_selection(selection, result)


def suggest_citations():
    selection, text = _get_selected_text()
    if not text.strip():
        return
    # For offline citation suggestions, ask the model to propose reference placeholders and short rationale.
    system = ('You are a research assistant with access to a local citation KB. ' 
              'Given the text, suggest up to 3 scholarly references (author, year, title) that would support the claims. ' 
              'Flag if you are unsure and provide a short justification. Return JSON.')
    prompt = text + '\n\nRespond in JSON array: [{"citation":"Author, Year, Title","why":"short reason"}, ...]'
    raw = _call_local_llm_chat(system, prompt)
    # A safe parsing step
    try:
        citations = json.loads(raw)
    except Exception:
        # fallback: return the raw result
        _replace_selection(selection, raw)
        return
    formatted = '\n'.join([f"- {c.get('citation')} — {c.get('why')}" for c in citations])
    _replace_selection(selection, formatted)


# Expose functions to LibreOffice
g_exportedScripts = summarize_selection, grammar_fix_selection, suggest_citations

Notes about the script

  • We use environment variables LOCAL_LLM_API and LOCAL_LLM_KEY so you can point to any local server without editing code.
  • The script supports simple JSON parsing for citation suggestions; in production you should validate model output and cross-check against a local bibliography.
  • If LibreOffice’s embedded Python lacks requests, the code falls back to urllib.

Running a simple local LLM inference server (examples)

Choose one of these options depending on your laptop and model size. In 2026 these are standard:

1) OpenAI-compatible local server

Many local runtimes expose the /v1/chat/completions endpoint. Start it and set:

export LOCAL_LLM_API=http://127.0.0.1:8000/v1/chat/completions
export LOCAL_LLM_KEY=

Examples of runtimes: text-generation-inference, Ollama-style local servers, or a small Docker container built around a lightweight 2026 model.

2) llama.cpp / ggml with a tiny wrapper

If you run a local ggml model with a minimal wrapper (e.g., llama-cpp-python or a tiny Flask wrapper), expose a simple POST /chat endpoint that accepts prompt and returns text. A single-file Flask app can do this and run on low-end machines.

3) GPT4All or MLC-LLM on-device

These toolchains typically ship with a small HTTP server option. They are ideal for students with modest hardware.

Packaging the extension (.oxt)

LibreOffice extensions are ZIP files with a specific structure. Minimal required files are META-INF/manifest.xml and your Python folder.

<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
  <manifest:file-entry manifest:full-path="/" manifest:media-type="application/vnd.sun.star.extension"/>
  <manifest:file-entry manifest:full-path="python/" manifest:media-type=""/>
</manifest:manifest>

Create description.xml to describe the add-on (LibreOffice will use it in the Extension Manager). Then zip the folder and rename to .oxt. Install via Tools → Extension Manager.

Adding toolbar buttons (optional)

To make the commands user-friendly, add entries to Addons.xcu that register the scripts and associate them to toolbar buttons. The easiest route for quick testing is Tools → Macros → Run Macro and select the functions. Packaging Addons.xcu is well-documented on The Document Foundation site.

Privacy and accuracy — important considerations

Local LLMs greatly reduce data exposure, but they can still hallucinate facts and citations. Always verify before using suggested citations in assignments.

Privacy checklist:

  • Run the inference server on the same machine or a trusted local network.
  • Use quantized models stored locally (4-bit quantization common in 2026) to avoid external requests.
  • Store any API keys or config in local protected files (use OS keyrings where possible).

Accuracy checklist:

  • For citation suggestions, integrate with a local bibliography (BibTeX / Zotero) to verify references before accepting them.
  • Do not rely on the model to assert facts without an explicit verification step against trusted sources.

Advanced options and improvements

1) Integrate Zotero or a local paper index

In 2026, many students use Zotero locally. You can query Zotero’s local SQLite DB or its HTTP connector to fetch verified citations. Use the LLM only to map claims to likely references and then run an exact match against your local index.

2) Chain-of-thought and few-shot templates

When asking for citation suggestions, include a few-shot template with examples so the model returns structured JSON every time.

3) Use model adapters or LoRA for domain adaptation

If you work in a specialized field, apply small-domain adapters to the local model (parameter efficient fine-tuning) to improve suggestion quality without requiring cloud training.

4) Offline embedding search for citation retrieval

Precompute embeddings for your local PDF library using an open embedding model, store them in a simple vector DB (FAISS/Annoy) on the laptop, and combine an embedding search with the LLM to produce grounded citations.

Troubleshooting common issues

  • No requests module: install requests into LibreOffice's Python or rely on the urllib fallback in the sample script.
  • Selection returns empty: ensure you are in the Writer document and text is selected. Multi-range selections require iterating through ranges (the snippet handles a simple fallback).
  • Local server not reachable: check LOCAL_LLM_API and firewall settings. Test with curl first.
  • Model returns non-JSON for citation suggestions: use stricter prompt engineering and validate before replacement.

Example student workflow (real-world case)

Maria, a final-year student in 2026, needs to write a 2,500-word essay. She:

  1. Installs the LibreOffice add-on built here.
  2. Runs a local quantized 7B open model on her laptop via a small inference server.
  3. Selects a paragraph, clicks Summarize selection to get concise bullet points for an annotated bibliography.
  4. Uses Grammar fix to tighten sentences, then runs Suggest citations. The add-on returns three candidate references. Maria uses the Zotero integration to verify and imports the matched items into her bibliography.
  5. She exports her final paper with verified citations and never uploads drafts to the cloud — satisfying her university's privacy requirement.

Actionable takeaways (what to do next)

  • Start with the sample script above and point LOCAL_LLM_API to a running local server.
  • Test the summarize and grammar commands on short paragraphs before scaling to full documents.
  • For citations, set up a local Zotero or BibTeX library and build a small verification step that cross-checks model outputs.
  • Package your add-on as .oxt and share with classmates for feedback — iterate on prompts and UI placement.

Future-proofing and 2026 recommendations

Expect local model quality to continue improving: smaller models will get better at reasoning, and federated or hybrid models (local + privacy-preserving cloud verification) will become standard. Design your extension to be API-agnostic so you can swap the backend as new runtimes appear.

Recommendation for students: keep the model and citation verification separate. Use the LLM for drafting and idea generation; use your local index for fact-checking and final citation insertion.

Safety and academic integrity

Using LLMs to draft or edit work is a tool — not a replacement for learning. Follow your institution's policies on AI usage. When in doubt, add a short author note about AI assistance.

Final checklist before you submit

  • Did you verify all suggested citations against trusted sources?
  • Is your inference server running locally or on a trusted network?
  • Have you packaged and tested the .oxt on at least one other machine (compatibility check)?

Wrapping up

Building a LibreOffice extension that talks to a local LLM is now practical for students and privacy-conscious users. This tutorial gave you a minimal, installable workflow and concrete next steps: connect a small local server, test the Python macro, and integrate a local citation index to avoid hallucinations.

Want a ready-made starter kit? I packaged a reference implementation for classroom use and maintenance-friendly prompts tuned for summarization and citations. If you want it, follow the call-to-action below.

Call to action

Ready to build your own private AI assistant in LibreOffice? Download the starter kit, or sign up for our hands-on workshop where we walk students through model setup, Zotero integration, and packaging extensions for distribution. Get the starter kit, test it on your machine, and share feedback so we can publish a classroom-ready version for 2026 coursework.

Advertisement

Related Topics

#AI#productivity#open-source
U

Unknown

Contributor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

Advertisement
2026-03-02T01:33:03.047Z