Skip to content

Efficient XML edit - design doc #2

Description

@eric-burel

Goal : create a set of functions to edit a docx document.

They are designed to be AI friendly, with a low token consumption and a simple interface.

Reading a document efficiently

  • We want to turn the XML content of the docx document into a paginated list of text elements.
  • Each element must be associated with a selector, for later editing.
  • Using the actual selector is inefficient: it is too long, and can't be properly read by the LLM (which thinks in tokens)
  • We should replace the actual selectors with a short, unique, human-readable random string, that can be used to identify the element later.
  • The lowest level of selector is always favored, so we never alter the structure and destroy nested nodes.

Example expected result of reading the document:

    {
'/w:document/w:body/w:p[1]/w:r/w:t': 'Title', 
'/w:document/w:body/w:p[2]/w:r/w:t': 'P1 content', 
'/w:document/w:body/w:p[3]/w:r[1]/w:t': 'P2 content with', 
'/w:document/w:body/w:p[3]/w:r[2]/w:t': 'bold', 
'/w:document/w:body/w:p[3]/w:r[3]/w:t': 'text', 
'/w:document/w:body/w:p[4]/w:r/w:t': 'Li1',
 '/w:document/w:body/w:p[5]/w:r/w:t': 
'Li2', '/w:document/w:body/w:p[7]/w:r/w:t': 'Page 2 content'
}

Example expected data sent to the LLM, with pagination to avoid longer prompts:

    {
        page: 1,
        pageNb: 10,
        data: {
            'alpha': 'Title', 
            'bravo': 'P1 content', 
            'charlie': 'P2 content with', 
            'delta': 'bold', 
            'echo': 'text', 
             'foxtrot': 'Li1',
             'golf': 'Li2', 'hotel': 'Page 2 content'
            }
    }

To go further, we can just combine letters : "alpha alpha", "alpha bravo", "alpha charlie", etc. This is a bit more complex to implement, but it is more readable and easier to handle for the LLM. The tricky part is to guarantee unicity if order changes.

Alternative choice for LLM visible selectors:

  • We could just generate random strings like those used to name random Docker container. They might be longer on average though, especially for longer documents.

Handling "runs" in paragraphs

Since we use the lowest possible selectors to avoid altering the document structure, words may be cut into pieces.

Here are the selectors for the word "Bonjour" but with 2 letters in a different style ("nj" in bold):

{
            '/w:document/w:body/w:p/w:r[1]/w:t': 'Bo',
            '/w:document/w:body/w:p/w:r[2]/w:t': 'nj',
            '/w:document/w:body/w:p/w:r[3]/w:t': 'our'
}

A "run" is an intermediate structure that holds these 3 pieces of text.

Maybe smarter LLMs will understand that automatically. Since multiple tool calls can be triggered in parallel, they could be smart enough to modify the 3 blocks.

If not, we will have to investigate how to reintroduce some sort of hierarchy without allowing the LLM to destroy style. For instance we could list the "runs" in read-only mode but force the model to read the inner text nodes too before triggering an edit.

Text replace

Edition should be done using selectors, so the LLM can't break structure.

More advanced edits like editing a run with multiple text nodes would be evaluated later on.

Edits

More generic edits like deleting paragraphs or adding them should be out of scope for now.
It's not certain that the selector-based approach is suitable for that purpose, as the XML hierarchy do matter for such edits.
It should be noted that editing a document will alter the selectors, so if a selector based approach is fit, the selectors might need to be completely updated on any change.

Python-docx can be used for implementation.

Documentation

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions