Practice

Writing tools with an AI assistant

The Ask AI panel, what it sends and what it writes, and how to get the same result from any chatbot you already use.

The shipped pyACAD tab has an AI (beta) panel. Its Ask AI button opens a docked palette with a chat that writes pyACAD tools from a plain-words request. It is a convenience over the same contract this guide describes, not a different way of building tools: what comes back is a bundle.yaml, a script.py and sometimes a config.py, written into an ordinary *.pushbutton folder you can open, edit or delete like any other. This page explains the mechanism so you can trust it, tune it, or reproduce it with another assistant.

The panel holds three things:

ButtonKindDoes
Ask AI*.dockpaneShows or hides the chat palette. AutoCAD remembers where you docked it.
AI Settingspushbutton, PYAISETTINGS Opens the Settings window on its AI assistant (beta) section.
Open AI Folderpushbutton, PYOPENAIFOLDER Opens the folder the generated tools live in, in Explorer. Before the first tool exists it toasts No AI tools yet instead.

The panel is a beta, and not the only way#

The panel is new and lightly tested, and says so at the top. Read every script it proposes before you click Create. And you do not need it at all: any assistant that can read a long document will write pyACAD tools once it has the authoring guide. The palette's footer has a Copy guide button that puts that exact guide on the clipboard, with no key needed, and the last section says what to do with it.

Setting it up#

Click AI Settings. The section has five fields:

FieldWhat to enter
ProviderAnthropic, or OpenAI-compatible for OpenAI itself, Azure OpenAI, Ollama, LM Studio or any server that speaks the chat-completions API.
Base URLEmpty for the provider's public endpoint (https://api.anthropic.com or https://api.openai.com/v1). For another OpenAI-compatible server, its address ending in /v1.
ModelPick one from the list or type any name. Empty uses the provider's default: claude-opus-5 for Anthropic, gpt-4o for OpenAI-compatible.
API keyYour key. Test sends a short request with the values in the window, saved or not, and shows the model's reply. Remove deletes a saved key. A local server needs no key.
Longest answer (tokens)The reply length limit, 8192 by default. Raise it if the panel says an answer hit the length limit.

Saved settings apply from your next message; no Reload is needed for them.

Where the settings and the key live#

Provider, base URL, model and answer length are the "ai" section of %APPDATA%\pyACAD\config.json. Only values that differ from the defaults are written:

%APPDATA%\pyACAD\config.json
{
  "ai": {
    "provider": "openai",
    "baseUrl": "http://localhost:11434/v1",
    "model": "qwen2.5-coder:14b",
    "maxTokens": 16000
  }
}

The API key is not there. It is in %APPDATA%\pyACAD\secrets.json beside it, encrypted with Windows DPAPI for your Windows account, so a config.json that gets shared, screenshotted or pasted into a bug report never carries a key, and a copied secrets.json is useless on another account. A key that cannot be decrypted reads as no key.

The key is saved for the endpoint it was entered with (its scheme, host and port) and is only ever sent back there, and only over https or to a server on this computer. Switch provider or point the panel at another server and the saved key stays behind rather than travelling with you; enter a key for the new endpoint. Typing a key for a plain-http server elsewhere on the network is refused with Keys are only sent over https, or to a server on this computer.

Using the panel#

  1. Click Ask AI. Without a usable key the palette says so and offers Open AI settings.
  2. Describe the tool in the box at the bottom and press Send (or Ctrl+Enter). Tick Attach selection first if the tool works on the kind of objects you have selected. The reply streams in; Cancel stops it.
  3. When the reply contains a complete tool, a card appears under it with Create tool and View files. If the model asked a question instead, answer it.
  4. Create tool writes the bundle and reloads the ribbon. The tool is on the AI tab, and like every bundle it is also an AutoCAD command (PY plus its title).
  5. Keep talking to change it. Each revision brings a new card that reads Update tool, which rewrites the same folder.

The conversation survives the Reload that Create triggers: the chat lives for the AutoCAD session, not for the palette's content. New tool in the header starts over.

Coming back to a tool#

The header's Edit an existing tool list holds every tool in the AI extension. Picking one starts a fresh conversation that is already editing it: its current files ride along with your next message, and the card reads Update tool. Nothing is sent until you type.

The fix loop#

Every script that fails has its traceback kept for the session, per bundle folder. When the tool the conversation is editing has one, the transcript offers Send last error to AI, which sends the traceback with a request for complete, fixed files. Creating or updating the tool clears the recorded error.

What the model is given#

Every request carries a system prompt in two parts:

  • The output rules. Answer with one bundle; a Title: line; an Icon: line naming one or two letters and a colour; one fenced block per file with the file name in the fence's info string; no other files, folders or paths; no if __name__ == '__main__':; top-level code; toasts, never print(); a silent cancel; ask one short question rather than guess.
  • The authoring guide. authoring-pack.md, shipped beside the pyACAD runtime and generated from the same sources as this site: what a bundle is, the rules, the failure modes, script templates and the pyacad API reference.

The system prompt is identical on every turn. With Anthropic it is sent marked for prompt caching, so from the second message on most of it is a cache read rather than new input.

Then the conversation so far. When it grows past about a hundred thousand characters the oldest exchanges are dropped first; your newest message always goes.

When you tick Attach selection, your message also carries a summary of the current selection: how many objects are selected, and for each object type (by DXF name, such as INSERT or LWPOLYLINE) the property names of its .NET class. Names only, never values: no layer names, no attribute text, no coordinates, no file names or paths. At most 200 objects are read and the summary stops at about four thousand characters. It tells the model which properties exist without sending anything from your drawing.

When the conversation is editing a tool, your newest message also carries that tool's files exactly as they are on disk, so the model edits what exists rather than what it remembers.

Nothing else goes. Requests go from AutoCAD straight to the endpoint you configured: POST /v1/messages for Anthropic, POST /chat/completions for OpenAI-compatible servers, streamed. pyACAD runs no service in between and keeps no copy of the conversation beyond the open session.

What comes back, and what is written#

The reply is parsed for fenced blocks named bundle.yaml, script.py or config.py, named in the fence's info string or on the line just above it. Any other file name is refused and never written. One unnamed block that reads as Python is taken as script.py. A reply with no script.py, or no title, is a question or a comment, not a proposal, and gets no card.

Create writes the bundle into the AI extension, which lives in the per-user extension root pyACAD always scans:

folders
%APPDATA%\pyACAD\extensions\AI.extension\
  extension.yaml                    name: AI, engine: ironpython
  AI.tab\
    Generated.panel\
      Isolate_Walls.pushbutton\
        bundle.yaml
        script.py
        config.py                   only when proposed
        icon.png  icon.dark.png  icon.small.png  icon.small.dark.png
  • The folder name is the title with spaces and hyphens turned into underscores and everything but letters, digits and underscores removed, at most 40 characters. A title starting with a digit gets Tool_ in front, so it is never read as an ordering prefix. An existing name gets _2, _3 and so on.
  • The icons are painted by pyACAD itself: a rounded square in violet, teal, amber, rose or green with the letters from the Icon: line. Blue is deliberately not in that set, so a tool the assistant wrote never passes for a shipped one.
  • A missing bundle.yaml is written as just title: <Title>.
  • The write is atomic. Files go to a temporary folder that is moved into place in one step, so a Reload never meets a half-written tool.

Update rewrites the files in the same folder, so the bundle key, its command name (when the title is unchanged) and any keyboard shortcut bound to it survive. A config.py the new proposal no longer includes is deleted. The writer refuses to touch any folder outside AI.extension.

Generated tools are yours

Once written, a generated tool is an ordinary bundle. Edit it by hand, move it into your own extension (it then leaves the panel's list), or delete its folder and Reload. Updates of pyACAD replace pyACAD.extension only, never AI.extension.

When a reply goes wrong#

You seeMeans
An error with an Open AI settings buttonThe server rejected the key. Check it in AI Settings and use Test.
The answer hit the length limit.The reply was cut off at Longest answer. Raise it and ask again.
The model declined this request.The provider refused to answer.
No card under a replyThe reply had no script.py or no title. Ask for the complete files.
Could not write the tool: ...The bundle could not be written to disk; the reason follows.

Request failures are also written to %APPDATA%\pyACAD\logs\pyACAD.log as AI request failed.

Doing the same with any assistant#

  1. In the Ask AI palette, click Copy guide. The clipboard now holds the output rules and the authoring guide. (The footer's Docs button opens this page.)
  2. Paste it into your chatbot as the first message, or as a project or custom instruction if it supports one, then describe the tool.
  3. Save what comes back as files: make a folder <Name>.pushbutton under a panel in one of your extensions, put script.py and bundle.yaml in it, and click Reload. See Your first button for the folder chain and Icons for the art; without an icon the button still works, it just shows its caption only.
  4. If it fails, copy the traceback from the output window or the log back into the chat.

The guide is what makes this work. A model that has not read it will happily wrap the code in if __name__ == '__main__': (which never excludes anything in pyACAD, so it is noise at best), print() its result (which opens an output window for one line) and invent library functions. With it, the answer usually runs first time.