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
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
Example expected result of reading the document:
Example expected data sent to the LLM, with pagination to avoid longer prompts:
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:
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):
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