AI News HubLIVE
站內改寫6 分鐘閱讀

待翻譯:Building a better PowerPoint API for the AI era

AI 服務暫時不可用,以下為來源摘要,待恢復後補全翻譯:tldr AI agents create and edit PowerPoint by writing code against libraries with serious limitations. Even basic edits end up slow, expensive, and prone to file corruption. We built a PowerPoint API that addresses these…

來源Hacker News AI作者: danielochoa0620

AI 服務暫時不可用,以下為來源正文,待恢復後補全翻譯。

tldr AI agents create and edit PowerPoint by writing code against libraries with serious limitations. Even basic edits end up slow, expensive, and prone to file corruption. We built a PowerPoint API that addresses these limitations, making it easier for AI agents and humans to work with PowerPoint programmatically. On our benchmark of 195 tasks, every model performed better with these tools. And the cheapest model (Luna) beat the best frontier model writing code (Opus), at 96% lower cost, with zero corrupted files. Code generation: the status quo and its problems AI agents generate code to create and edit PowerPoint files. There are two main approaches: Automating with the PowerPoint application. The entry points are VBA macros, VSTO (the older generation of Windows add-ins in C#), and Office.js (the modern generation of add-ins – JavaScript web apps running in an embedded browser in the task pane). All of these trigger actions in a running instance of PowerPoint faster than you can click and type. Editing the raw Office Open XML (OOXML) inside the .pptx file. A .pptx is a ZIP of XML files (see this explainer if you need an intro), so technically any programming language that does text replacement can make changes to the file. The OOXML spec is notoriously difficult, so people use libraries that wrap the XML in a more intuitive object model. The most popular by far is the open-source python-pptx. You write textbox.text = "Hello world" and the library handles the XML. The second approach doesn't need the PowerPoint application, which is unreliable to run on a server. That makes it the natural fit for AI agents executing code in a sandbox, and given the popularity of python-pptx, agents do the bulk of their work with Python scripts. The problem is that python-pptx has real gaps. For example, it can't copy a slide, add or remove table rows, create a chart with two value axes, or replace text without collapsing all its formatting into a single run. To make up for the gaps, agents fall back to editing the raw XML: unzip, string-replace, and rezip. It's like performing open-heart surgery on the file, and the risks range from silent no-ops to full-blown file corruption. Ask anyone who's worked with OOXML – it's a minefield of tangled inheritance chains, strict element ordering, and confusing rules. And even if you get that right and follow the rules, PowerPoint may disagree about what counts as valid OOXML and flag your file as corrupted even though it passed your validation checks. It runs the other way too: PowerPoint will happily open files that violate the schema, and many of these divergences are undocumented. Here's an example to illustrate. You create a 100% stacked column chart to show how revenue proportions are evolving. Naturally, you want to add data labels so your audience actually sees how the exact numbers change. You've worked with pie charts before, so you know that PowerPoint lets you add data labels containing the percentage values – a natural choice for a percentage chart. It should be a straightforward XML operation. You locate the data labels element for the chart: Flip . You're extra cautious so you run the new chart1.xml through an OpenXML validator and you get the green light. Easy. You open the modified file: Nothing changed. Confused, you pull up the official schema ISO specs, flip to page 4,061, and see clear as day that you did in fact provide valid XML. It turns out that PowerPoint doesn't render percentage data labels for 100% stacked column charts. You have to manually set the data as percentages, add value data labels, and format the number. Now let's say you want to try something truly egregious like setting the data label position to "best fit." You go to the same data labels XML and very carefully insert the data label position element. You double-check again with page 4,061 of the specs. Good, you inserted before showLegendKey – anything else and you corrupt the file. You run the validator and you get the green light again. This time when you open PowerPoint: This is the OOXML minefield. Seemingly benign operations that are schema-correct have catastrophic consequences. The only reliable defense is to look at what PowerPoint actually renders. A render also resolves information you can't deduce from just looking at the file, like how much space an auto-fit textbox ends up using. But you can't run PowerPoint on a server easily or reliably, so the standard trick is LibreOffice – the open-source Office alternative – which runs headless on a server and renders slide screenshots. It's not perfect fidelity, but it beats flying blind. So the standard playbook looks like this: run python-pptx scripts, hand-edit raw XML where the library falls short, and screenshot LibreOffice renders to catch what the file content doesn't show. With today's frontier models like Opus and Sol, you get decent results, but it's slow and expensive. You pay for a huge volume of output tokens and wait through a lot of turns of the edit-render-inspect-repair loop. It's complicated enough that Anthropic and OpenAI don't trust their affordable models to be useful. Claude doesn't let you pick Haiku from the model selector in their PowerPoint add-in. ChatGPT doesn't even offer Terra in theirs. The tool-based approach We started building editide in early 2025, when agents were just LLMs, and they produced terrible PowerPoint slides. The models were smart enough to understand the general direction of a PowerPoint task but were drowning in the execution. You could take a screenshot of a slide, paste it into ChatGPT, ask for suggestions and it would dish out decent advice. "Add data labels to your chart and delete the commentary textbox." But attach the .pptx and ask it to make the changes itself, and it would take ten minutes to hand back a corrupted file. If you were lucky, you got something that opened, but still took longer to clean up than doing it yourself. Our idea was to tap into the model's intent and build tools that handled the messy code implementation. Instead of chaining together hundreds of lines of code, the model would use simple JSON tool calls. The first step was to get LLMs to see what they were working with. Start with the most basic slide there is – the empty title slide PowerPoint gives you when you create a new presentation. You'd want the LLM to know that there's an empty slide with two placeholders. Something like: { "text_boxes": [ { "id": 2, "placeholder_text": "Click to add title", "position": {"left": 120, "top": 88.38, "z_index": 0}, "size": {"height": 188, "width": 720}, "text": "", "font_size": 60, "font_family": "Calibri" }, { "id": 3, "placeholder_text": "Click to add subtitle", "position": {"left": 120, "top": 283.63, "z_index": 1}, "size": {"height": 130.37, "width": 720}, "text": "", "font_size": 24, "font_family": "Calibri" } ] } 235 tokens Here's the actual slide1.xml: 1,200 tokens Just by looking at the XML, can you figure out what font sizes the title and subtitle use? How about their X or Y coordinates telling you where the text boxes are positioned? Don't worry, it's not that you're bad at OOXML. The answers are hiding in three other files: slideLayout1.xml, slideMaster1.xml, and theme1.xml. You have to trace through the inheritance chain to extract the position from the layout, the font size from the master, and the font family from the theme. It's confusing for LLMs too. But even if they figured out that they had to check four different files to read one slide, it would be insane to do that every time when you can solve it once with code. So the first tool we built was read_slide: the slide as JSON with the inheritance chains already flattened – actual positions, actual fonts, actual sizes. Editing is the same picture in reverse. The model changes a property on the JSON it just read, and the engine writes the correct XML. { "text_boxes": [ { "id": 2, "text": "Hello, world!" } ] } We set out to build tools for every property you would want to change – the equivalent of rewriting the entire PowerPoint application with a JSON interface. We did it property by property, poring through 5,000 pages of specs and manually uncovering edge cases where PowerPoint breaks its own rules. Things like the bestFit landmine from earlier were encoded once, instead of rediscovered by every agent in every session. Beyond the basics Once we were able to edit basic properties reliably, we moved on to more advanced tools that compress an entire workflow into a single call. Tools that do for agents what traditional add-ins like think-cell do for humans: turn a tedious multi-step chore into one action. Take one of the most common workflows: adding a chart. Formatting one from scratch takes dozens of decisions – colors, axes, labels, etc. In the real world, humans rarely do the full process. They usually copy an existing chart, update the Excel data, and clean up as needed. So we built a tool that creates a new chart from an existing one as a template. In one call it copies the chart (even across decks), preserves all formatting, clones the embedded Excel workbook, and writes the new data into both the chart cache and the workbook. Here's that call next to the python-pptx + XML equivalent. Both are real transcripts of GPT-5.6 Sol running the same task to add a new chart based on a chart in another deck. The editide tool call: { "slide_index": 1, "slide": { "chart_elements_to_add": [{ "template_presentation_id": "Column Chart", "template_slide_index": 1, "template_element_id": 5, "position": {"left": 74.18, "top": 186, "z_index": 1}, "size": {"width": 938.95, "height": 363.04}, "category_renames": [ {"index": 0, "new_name": "North"}, {"index": 1, "new_name": "South"}, {"index": 2, "new_name": "East"}, {"index": 3, "new_name": "West"} ], "series_list": [ {"index": 0, "new_name": "Atlas Foods"}, {"index": 1, "new_name": "Beacon Markets"}, {"index": 2, "new_name": "Crest Retail"} ], "chart_data": { "0:North": {"0:Atlas Foods": 18.2, "1:Beacon Markets": 14.6, "2:Crest Retail": 9.8}, "1:South": {"0:Atlas Foods": 21.5, "1:Beacon Markets": 16.9, "2:Crest Retail": 11.2}, "2:East": {"0:Atlas Foods": 19.8, "1:Beacon Markets": 17.3, "2:Crest Retail": 12.7}, "3:West": {"0:Atlas Foods": 24.1, "1:Beacon Markets": 20.4, "2:Crest Retail": 14.9} } }] } } The code-generation call: from pathlib import Path from zipfile import ZipFile, ZIP_DEFLATED from lxml import etree import shutil, tempfile DST = Path('/work/deck.pptx') SRC = Path('/work/assets/Column Chart.pptx') NS = { 'p':'http://schemas.openxmlformats.org/presentationml/2006/main', 'a':'http://schemas.openxmlformats.org/drawingml/2006/main', 'c':'http://schemas.openxmlformats.org/drawingml/2006/chart', 'r':'http://schemas.openxmlformats.org/officeDocument/2006/relationships', 'pr':'http://schemas.openxmlformats.org/package/2006/relationships', 'ct':'http://schemas.openxmlformats.org/package/2006/content-types', 'x':'http://schemas.openxmlformats.org/spreadsheetml/2006/main', } CATS = ['North','South','East','West'] SERIES = [ ('Atlas Foods',[18.2,21.5,19.8,24.1]), ('Beacon Markets',[14.6,16.9,17.3,20.4]), ('Crest Retail',[9.8,11.2,12.7,14.9]), ] with tempfile.TemporaryDirectory() as td: td=Path(td); srcd=td/'src'; dstd=td/'dst' with ZipFile(SRC) as z: z.extractall(srcd) with ZipFile(DST) as z: z.extractall(dstd) # Copy the template chart and its embedded workbook intact first. (dstd/'ppt/charts/_rels').mkdir(parents=True, exist_ok=True) (dstd/'ppt/embeddings').mkdir(parents=True, exist_ok=True) shutil.copy2(srcd/'ppt/charts/chart1.xml', dstd/'ppt/charts/chart1.xml') shutil.copy2(srcd/'ppt/charts/_rels/chart1.xml.rels', dstd/'ppt/charts/_rels/chart1.xml.rels') shutil.copy2(srcd/'ppt/embeddings/Microsoft_Excel_Worksheet.xlsx', dstd/'ppt/embeddings/Microsoft_Excel_Worksheet.xlsx') [truncated for AI cost control]