DOCX creation, editing, and analysis
Execution Rules
- ALL code execution MUST use the
code_interpretertool. Do NOT use theshelltool. - NEVER call
!pip install.python-docx,boto3,pandas,openpyxl,Pillow,matplotlib,numpy,requests,lxmlare pre-installed in the AgentCore Code Interpreter sandbox. Import directly. If an import fails, stop and report the error to the user — do not attempt to install anything. - Generate the COMPLETE document and upload to S3 in a SINGLE
code_interpretercall. Do NOT split into multiple calls. - Before calling
code_interpreter, callartifact_path(filename="report.docx")to get the S3 bucket and key. - After completion, report the
artifact_refto the user. - If
code_interpreterfails with an error, do NOT retry automatically. Report the error to the user and ask for clarification or guidance. Do not make multiple retry attempts without user input.
Workflow
- Call
artifact_path(filename="report.docx")— returns{ s3_uri, bucket, key, artifact_ref } - Copy the actual
s3_uristring value from the artifact_path result and hardcode it as a string literal in your code_interpreter script. Do NOT use variable references — the code_interpreter runs in an isolated sandbox and cannot access the agent's tool results. - Call
code_interpreterONCE with a single script that does everything: create the document, save it, and upload to S3.
from docx import Document
import boto3
# IMPORTANT: Replace with the ACTUAL s3_uri value returned by artifact_path
S3_URI = "s3://my-bucket/user123/proj456/artifacts/art_abc123/report.docx" # ← paste the actual s3_uri here
# Parse S3 URI into bucket and key
BUCKET, KEY = S3_URI.replace("s3://", "").split("/", 1)
# Build entire document
doc = Document()
# ... all document content ...
doc.save('./output.docx')
# Upload to S3
s3 = boto3.client('s3')
with open('./output.docx', 'rb') as f:
s3.upload_fileobj(
f, BUCKET, KEY,
ExtraArgs={'ContentType': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'}
)
- Report the
artifact_refto the user
Overview
A .docx file is a ZIP archive containing XML files.
Quick Reference
| Task | Approach |
|---|---|
| Read/analyze content | Download from S3 → python-docx in code_interpreter |
| Create new document | Use python-docx in code_interpreter |
| Edit existing document | Unpack → edit XML → repack in code_interpreter |
Charts
When the user requests charts or visualizations, always attempt to embed charts directly using python-docx first. Only use the chart skill if direct embedding is not possible or the chart type is unsupported by python-docx.
python-docx does not natively support inserting Office charts. In practice, embed charts as images: generate the chart with matplotlib, save as an image, and insert with doc.add_picture().
import matplotlib.pyplot as plt
from io import BytesIO
fig, ax = plt.subplots()
ax.bar(['Q1', 'Q2', 'Q3', 'Q4'], [100, 120, 140, 160])
buf = BytesIO()
fig.savefig(buf, format='png', bbox_inches='tight')
buf.seek(0)
doc.add_picture(buf, width=Inches(5))
plt.close(fig)
Only fall back to the chart skill if a standalone chart artifact is explicitly required.
Reading Documents
Read .docx files by downloading from the given S3 path and using python-docx in code_interpreter.
import boto3
from docx import Document
from io import BytesIO
s3 = boto3.client('s3')
obj = s3.get_object(Bucket=bucket, Key=key)
doc = Document(BytesIO(obj['Body'].read()))
# Extract all text
for para in doc.paragraphs:
print(para.text)
# Extract tables
for table in doc.tables:
for row in table.rows:
print([cell.text for cell in row.cells])
Creating New Documents
Generate .docx files with python-docx in code_interpreter.
Setup
from docx import Document
from docx.shared import Inches, Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
doc = Document()
# ... build document ...
doc.save('./output.docx')
Page Size
from docx.shared import Inches
section = doc.sections[0]
section.page_width = Inches(8.5) # US Letter
section.page_height = Inches(11)
section.top_margin = Inches(1)
section.bottom_margin = Inches(1)
section.left_margin = Inches(1)
section.right_margin = Inches(1)
Common page sizes:
| Paper | Width | Height |
|---|---|---|
| US Letter | 8.5 in | 11 in |
| A4 | 8.27 in (21 cm) | 11.69 in (29.7 cm) |
Landscape orientation:
section.orientation = WD_ORIENT.LANDSCAPE
section.page_width, section.page_height = section.page_height, section.page_width
Styles
from docx.shared import Pt, RGBColor
style = doc.styles['Normal']
style.font.name = 'Arial'
style.font.size = Pt(12)
# Heading styles
h1 = doc.styles['Heading 1']
h1.font.size = Pt(16)
h1.font.bold = True
h1.font.color.rgb = RGBColor(0, 0, 0)
Paragraphs and Text
p = doc.add_paragraph()
run = p.add_run('Bold text')
run.bold = True
run.font.size = Pt(14)
# Heading
doc.add_heading('Section Title', level=1)
# Alignment
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
Lists
# Bullet list
doc.add_paragraph('Item 1', style='List Bullet')
doc.add_paragraph('Item 2', style='List Bullet')
# Numbered list
doc.add_paragraph('Step 1', style='List Number')
doc.add_paragraph('Step 2', style='List Number')
Tables
table = doc.add_table(rows=3, cols=3)
table.style = 'Table Grid'
# Set cell content
cell = table.cell(0, 0)
cell.text = 'Header'
# Merge cells
table.cell(0, 0).merge(table.cell(0, 1))
# Column widths
for row in table.rows:
row.cells[0].width = Inches(2)
row.cells[1].width = Inches(3)
Images
Tool selection:
- If
image___search_imageis available in your tool list, use it to find relevant images. - If
image___search_imageis NOT available, usegenerate_imageto create custom images.
Choose an appropriate number of images based on the document content, but do NOT exceed 5 images per document.
- Before
code_interpreter, callimage___search_image(orgenerate_imageif unavailable) to get image URLs. - Inside
code_interpreter, download the URLs and add to the document:
import requests
from io import BytesIO
# Download image from URL (obtained via image___search_image or generate_image)
resp = requests.get(image_url)
doc.add_picture(BytesIO(resp.content), width=Inches(4))
Page Breaks
doc.add_page_break()
Headers/Footers
section = doc.sections[0]
header = section.header
header.paragraphs[0].text = 'Document Header'
footer = section.footer
footer.paragraphs[0].text = 'Page Footer'
Critical Rules for python-docx
- Set page size explicitly - defaults may vary
- Use
add_paragraphwith styles for lists, not manual bullet characters - Use
Inches()orCm()orPt()for all dimension values - Table widths must be set per-cell for consistent rendering
- Save before uploading - always
doc.save()first
Editing Existing Documents
Use code_interpreter for all steps. Download the file from the given S3 path first.
Step 1: Download and Unpack
import zipfile
import os
import boto3
# Download from S3
s3 = boto3.client('s3')
s3.download_file(bucket, key, 'document.docx')
with zipfile.ZipFile('document.docx', 'r') as z:
z.extractall('unpacked/')
Step 2: Edit XML
Edit XML files in unpacked/word/. See XML Reference below for patterns.
Use "Claude" as the author for tracked changes and comments, unless the user explicitly requests use of a different name.
CRITICAL: Use smart quotes for new content. When adding text with apostrophes or quotes, use XML entities to produce smart quotes:
<!-- Use these entities for professional typography -->
<w:t>Here’s a quote: “Hello”</w:t>
| Entity | Character |
|---|---|
‘ |
' (left single) |
’ |
' (right single / apostrophe) |
“ |
" (left double) |
” |
" (right double) |
Step 3: Pack
import zipfile
import os
with zipfile.ZipFile('output.docx', 'w', zipfile.ZIP_DEFLATED) as z:
for root, dirs, files in os.walk('unpacked/'):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, 'unpacked/')
z.write(file_path, arcname)
Common Pitfalls
- Replace entire
<w:r>elements: When adding tracked changes, replace the whole<w:r>...</w:r>block with<w:del>...<w:ins>...as siblings. Don't inject tracked change tags inside a run. - Preserve
<w:rPr>formatting: Copy the original run's<w:rPr>block into your tracked change runs to maintain bold, font size, etc.
XML Reference
Schema Compliance
- Element order in
<w:pPr>:<w:pStyle>,<w:numPr>,<w:spacing>,<w:ind>,<w:jc>,<w:rPr>last - Whitespace: Add
xml:space="preserve"to<w:t>with leading/trailing spaces - RSIDs: Must be 8-digit hex (e.g.,
00AB1234)
Tracked Changes
Insertion:
<w:ins w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:t>inserted text</w:t></w:r>
</w:ins>
Deletion:
<w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:delText>deleted text</w:delText></w:r>
</w:del>
Inside <w:del>: Use <w:delText> instead of <w:t>, and <w:delInstrText> instead of <w:instrText>.
Minimal edits - only mark what changes:
<!-- Change "30 days" to "60 days" -->
<w:r><w:t>The term is </w:t></w:r>
<w:del w:id="1" w:author="Claude" w:date="...">
<w:r><w:delText>30</w:delText></w:r>
</w:del>
<w:ins w:id="2" w:author="Claude" w:date="...">
<w:r><w:t>60</w:t></w:r>
</w:ins>
<w:r><w:t> days.</w:t></w:r>
Deleting entire paragraphs/list items - when removing ALL content from a paragraph, also mark the paragraph mark as deleted so it merges with the next paragraph. Add <w:del/> inside <w:pPr><w:rPr>:
<w:p>
<w:pPr>
<w:numPr>...</w:numPr> <!-- list numbering if present -->
<w:rPr>
<w:del w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z"/>
</w:rPr>
</w:pPr>
<w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:delText>Entire paragraph content being deleted...</w:delText></w:r>
</w:del>
</w:p>
Without the <w:del/> in <w:pPr><w:rPr>, accepting changes leaves an empty paragraph/list item.
Rejecting another author's insertion - nest deletion inside their insertion:
<w:ins w:author="Jane" w:id="5">
<w:del w:author="Claude" w:id="10">
<w:r><w:delText>their inserted text</w:delText></w:r>
</w:del>
</w:ins>
Restoring another author's deletion - add insertion after (don't modify their deletion):
<w:del w:author="Jane" w:id="5">
<w:r><w:delText>deleted text</w:delText></w:r>
</w:del>
<w:ins w:author="Claude" w:id="10">
<w:r><w:t>deleted text</w:t></w:r>
</w:ins>
Comments
<!-- Comment markers are direct children of w:p, never inside w:r -->
<w:commentRangeStart w:id="0"/>
<w:r><w:t>commented text</w:t></w:r>
<w:commentRangeEnd w:id="0"/>
<w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>
CRITICAL: <w:commentRangeStart> and <w:commentRangeEnd> are siblings of <w:r>, never inside <w:r>.
Images
- Add image file to
word/media/ - Add relationship to
word/_rels/document.xml.rels:
<Relationship Id="rId5" Type=".../image" Target="media/image1.png"/>
- Add content type to
[Content_Types].xml:
<Default Extension="png" ContentType="image/png"/>
- Reference in document.xml:
<w:drawing>
<wp:inline>
<wp:extent cx="914400" cy="914400"/> <!-- EMUs: 914400 = 1 inch -->
<a:graphic>
<a:graphicData uri=".../picture">
<pic:pic>
<pic:blipFill><a:blip r:embed="rId5"/></pic:blipFill>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
Dependencies
python-docx, boto3, Pillow, lxml, matplotlib, requests are pre-installed in the Code Interpreter sandbox. Do NOT call !pip install — import directly.