This tutorial is part of the skills labs within Interactive Data Science and Visualization.

Advanced AI Techniques

Using advanced AI techniques for data visualization including llms.txt, agentic workflows / subagents, and task files.
Contents

Motivation

We've just seen how we can chat with an AI assistant in building simple visualizations but let's consider two potential challenges and two solutions.

Knowledge: What if you ask the AI assistant about a technology it perhaps doesn't know much about or maybe where that technology has changed since when the AI was built? There is a protocol called llms.txt which lets us provide new knowledge to assistants. It is kind of like a library for AI!

Context: What about building visualizations with novel representations or lots of customization where there's quite a bit more code or iteration? AI assistants have limited "context" or chat lengths. This can sometimes impair our ability to use them in more complex tasks. That said, some assistants like Claude allow for use of "subagents" which you can think of as allowing multiple AI assistants to coordinate with each other, saving any individual from over-burdening their context.

Reproducibility: If we have a lot of work with an AI, how can we document our work? We will discuss markdown and the use of "task files" that allow us to prompt assistants with more structure and documentation.

This tutorial will walk through use of llms.txt and, optionally, use of assistants to build out a more bespoke / tailored version of the wolves and moose visualization from the previous tutorial.

Setup

There are a few ways to pull this off but, if you are interested in using agents, going through Claude Code and the command line may be a good strategy. Otherwise, let's make sure you have what you need in order to do this entirely from the browser.

Using the browser

If you don't want to use the command line, it may be best to try this without agents. Don't worry though! We can still demonstrate the use of llms.txt and task files. Just make sure to have the web version of your assistant ready to go and use the Sketchingpy online sketchbook.

Preparing agents on the command line

If working with agents on the command line, the best place to get started is to create a new empty directory to do the work:

$ mkdir wolves_moose_viz
$ cd wolves_moose_viz

Then, write a requirements.txt file which is a way to denote what libraries are in use for that project (which is useful both to humans and AI working on the project with you):

$ echo "sketchingpy[static]" >> requirements.txt

In addition to Sketchingpy, we will also grab Pillow:

$ echo "pillow" >> requirements.txt

Let's also add some tools that can help us write cleaner Python code. The first is pyflakes which is a tool to automatically check Python files for issues that would prevent that code from running.

$ echo "pyflakes" >> requirements.txt

Let's also add pycodestyle which can check code for issues that might make it more difficult for other engineers (or AI!) to read the code even if the code is valid:

$ echo "pycodestyle" >> requirements.txt

Finally, it is often a good idea to use a virtual environment to keep your system clean:

$ python3 -m venv venv
$ source venv/bin/activate
$ pip install -r requirements.txt

If that isn't your preference though, you can install directly:

$ pip install -r requirements.txt

Lastly, go ahead and install your AI assistant's command line tool in a different terminal window. For this tutorial, we will use Claude Code. Please follow the install instructions and then start the assistant to ensure you are logged in.

$ claude

It is recommended that you start Claude from within the wolves_moose_viz directory.

Importing knowledge

First, let's tell our AI assistant about the latest version of Sketchingpy which we will use to perform the drawing.

Knowledge without agents

Most AI assistants can easily work with llms.txt just by giving a web path to where the file or files can be found. Generally both an llms-full.txt which is a longer but self-contained document is available along with llms.txt which is more of an index which can be used to find other information. We will stick with llms-full.txt for now! That in mind, let's go ahead and tell our assistant to pull in that information:

Hello! We will complete a series of tasks together using Sketchingpy. Please read https://sketchingpy.org/llms-full.txt?v=20250915 before we start so that you know how to use the library.

Note that some assistants might have limited internet access. If that's the case, you can download llms-full.txt files and add them as attachments to your chat. For more information, see the Sketchingpy llms documentation.

Knowledge with agents

We will have two agents: one that completes a task as described in a text file and another that checks the work of the first agent and fixes any issues with styling using pycodestyle and pyflakes. Depending on the assistant you are using, there might be different instructions for adding a new agent. For Claude, type /agents into the Claude Code interface and then add a new agent. When asked for description, try something like (note that, if you are not using a virtual environment, remove the comment about using venv):

This implementer agent should be given the path to a markdown file describing a task to complete. It should read that file and complete the task using Sketchingpy. In order to prepare to do its work, it should also then read https://sketchingpy.org/llms-full.txt?v=20250915. After it has gathered all of the necessary information, it should make a detailed todo list to complete the task assigned. Finally, when done, it should update the task markdown file with a description of what it did and if it encountered any issues. It should run the script before concluding work but it does not need to worry about any style issues which will be addressed by a later agent. Please call this agent `implementer`. Please ask it to use the venv.

You can use the recommended agent settings including to put this in the project folder and the option to provide a description but let Claude make the actual agent file. After done, go ahead and add a second agent:

This checker agent should be given the path to a markdown file describing a task that was just worked on by another agent. It should read that file and investigate the specific changes made to complete that task which should have been done using Sketchingpy. In order to prepare to do its work, it should also then read https://sketchingpy.org/llms-full.txt?v=20250915. After it has gathered all of the necessary information, it should double check and, where appropriate, improve that work. In addition to running the script to ensure it executes successfully, it should also run pyflakes followed by pycodestyle to resolve any problems identified. Please have it update the task file with any issues encountered and report overall status to the agent which called it. Please call this agent `checker`. Please ask it to use the venv.

Having added these two agents, we now have kinda a "special purpose" AI assistant for implementing our ideas and another to check that work and improve it. We are splitting across these two so that neither on their own run out of room in their context. However, we are also giving them instructions they should follow each time to ensure success.

Scaffolding

Alright we should be ready to take our first step which is just to sketch out the outlines of the code we want to have draw this visualization. This will let us dictate the code's architecture and then fill it in progressively through subsequent tasks.

That objective in sight, consider that many AI assistants use Markdown as a way to converse with the user and to internally represent conversation. Indeed, agent files are just markdown! Anyway, this is a lightweight way to structure text where # means a top level header, ## means a sub-header, ** means bold, the tick character means code, and - means a list. There are resources to learn how to use Markdown with its many features but we will try to stick to that for now. Specifically, we will make Markdown files each with an individual task to be completed by our AI assistant.

All that in mind, let's make a new text file called 01_scaffold.md. Then read and place the following inside:

# Table presenter Please create a class TablePresenter which takes in a sketch and which has stubbed methods and make docstrings for the given description (please use Google-style docstrings). Please have these simply throw NotImplementedError as we will fill them in as we go along. Please wrap at 80 characters. `__init__(self, sketch: sketchingpy.Sketch2D, data: list[dict])` Create a presenter to draw a visualization of the given data into the given sketch where records in the data look like {"year": 1980, "wolves": 50, "moose": 664}. `draw(self)` Draw the table with inline numbers with bars below that text. This will make a "tornado" chart which has one series (wolves) going left and the other (moose) going right. `_draw_top(self)` Draw the top row in black text for Year, Wolves, and Moose. `_draw_bottom(self)` Draw the bottom scale showing 0 to 500 for wolves and 0 to 1800 for moose. `_draw_body(self)` Draw year, wolves, and moose: - Year is on the left-most column centered. - Wolves is on the next column right aligned with bars going left. - Moose is on the far right column with left aligned but bars going right. `_get_y_pos(self, year: int) -> float` Get the y position corresponding to a year which is to be used in all series. `_get_length(self, series: str, count: int) -> float` Get the length of the bar to show for a series (wolves, moose) in pixels. Error thrown if the series is unknown or unsupported. `_get_color(self, series: str) -> str` Get the hex code to use for a given series (wolves, moose, year). Error thrown if the series is unknown or unsupported. `_get_max(self, series: str) -> float` Get the maximum count for a series (wolves, moose, year). Error thrown if the series is unknown or unsupported.

Scaffolding without agents

If not using agents, attach your markdown file as an attachment to your chat and ask your assistant to complete that task.

Great! Next, please read and complete the attached task file. Please prepare a Python script.

When done, take the Python code generated and double check it! Maybe confirm that, even if it doesn't do anything yet, that the script runs and finishes without error. This is a similar cycle of iteration we saw in our earlier experiments with AI! A NotImplementedError may be anticipated.

Scaffolding with agents

If using agents, go ahead and run Claude Code in the directory in a new chat and ask it something like the following:

Hello! Please have the implementer agent followed by the checker agent complete `01_scaffold.md`. However, please first learn more about Sketchingpy by reading https://sketchingpy.org/llms-full.txt?v=20250915.

This should have the AI assistant (the "coordinating agent") invoke the implementer agent first to write out the "first draft" of the code for our task. Then, when the implementer is done, it should invoke a checker agent to confirm the work. Note that implementer and checker both have their own context that is cleared after their invocation is done.

Data

Alright we have our scaffold here, let's bring in our data. Following the same flow, let's make a 02_data.md:

# Load data In this next step, we should gather the required data and give our sketch access to that file. ## Gather data Please write a `wolvesMoose.csv` which contains the table at https://www.nps.gov/isro/learn/nature/wolf-moose-populations.htm with the columns year, wolves, and moose. ## Add to script Next, I would like to update our script to create a sketch that is 400 pixels wide by 650 pixels tall. Let's have this cleared with a background of color #FFFFFF. Let's then use the data layer to load the file `wolvesMoose.csv` which we will plan to pass into the class we just constructed along with the sketch. Please initialize an instance of our presenter and call its draw method before calling show on the sketch. We do not need interactivity on this sketch, it should just write to a `wolvesMoose.png` file. Please leave our class stubbed except in the constructor where we should save data and sketch as private instance variables. Please leave all of this in one script file. ## Notes If needed, prior task markdown files are present in this directory like `01_scaffold.md`.

As before, make sure to review the output and run the script. Note that, if your agent cannot access the information from the internet, you can copy from that table into spreadsheet software and provide it as a CSV directly.

Draw outlines

Now that we have gotten through so much of that set up, we are in a really good position to start doing some drawing. Let's make a 03_draw_outline.md:

# Draw outline Let's draw the axes for our sketch. Let's not use push / pop style or transform. This should be able to run just to draw the top and bottom of the sketch. ## Define constants At the top of the file after imports, let's define some constants: - Let's move our min and max values for wolves and moose to constants `MIN_WOLVES` (0), `MAX_WOLVES` (50), `MIN_MOOSE` (0), and `MAX_MOOSE` (2400). - Let's also define the colors we want to use as `BACKGROUND_COLOR` (#F0F0F0), `AXIS_COLOR` (#333333), `WOLVES_COLOR` (#D95F02), and `MOOSE_COLOR` (#7570B3). - Let's also use the `Public Sans` font (`FONT`) with `AXIS_SIZE` as 15px (15) and `BODY_SIZE` as 13px (13). - Let's also create some common spacing where the center of the first column for year is at x coordinate of 55px (`YEAR_X = 55`), the right align target for wolves is x coordinate of 245px (`WOLVES_X = 245`) and the align target for moose is 255px (`MOOSE_X = 255`). The width of the year column should be 50px (`YEAR_WIDTH`) and columns for wolves and moose should be 140px wide (but no constant needed since we will use the value inline). - Let's also use `WIDTH` and `HEIGHT` for sketch dimensions. - Let's say that the header and footer height (`AXIS_HEIGHT`) is 30px. Please group the constants together by similar function and sort alphabetically within the groups. ## Draw Next, let's go ahead and have `draw` start with calling `_draw_top` and `_draw_bottom`. Let's then take care of drawing both the top and bottom. ## Draw Top Let's then implement `_draw_top` to use axis color and to draw the text Year, Wolves, Moose at the center, right, and left coordinate for those three columns at 50px y coordinate from the top (with 20px reserved for title). Let's set those back vertically by 5px and have a horizontal line in axis color drawn spanning the extent of the column for each. Please use `AXIS_SIZE` for all text in this section. Text should use fill and no stroke while line should use no fill and stroke. _Note_: We should have text aligned to bottom 45px from the top and the line at 50px from the top of the sketch. ## Draw Bottom Let's then implement `_draw_bottom` to use axis color to draw a horizontal line 30px y from the bottom spanning the length of each of the three columns. Below the wolves and moose columns let's draw the min and max for each with: - 0 at the far right for wolves and max at the far left of the column span - 0 at the far left for moose and max at the far right of the column span Please use `BODY_SIZE` for text in the bottom. Text should use fill and no stroke (clear_stroke) while line should use no fill and stroke. Please draw the text color using the series specific color but use `AXIS_COLOR` for year. To do this, please implement `_get_color` and use the series name to get the color to use. ## Notes Please use `AXIS_HEIGHT` in both draw bottom and draw top. Also, if needed, prior task markdown files are present in this directory like `01_scaffold.md`. Please use the font at http://mooc.interactivedatascience.courses/support/web/PublicSans-Regular.otf.

Please execute this using the agent or manual flow from before and check the result. Don't forget that your coordinating agent (the one you talk to directly) can also assist with edits like if something feels too cramped or not quite right! For example:

The 1980 seems to start too far down. Can you have the years start maybe 15 pixels higher?

Draw years

Next, we can start filling in some of the elements from the data themselves, starting with the years. Let's make a 04_draw_years.md:

# Draw years Next I would like to draw the years as the first part of _draw_series. ## Method Let's loop through the data we saved and get that year's y coordinate can be found by using _get_y_pos which should do a linear scaling from the top of the sketch to the bottom: `top_line + padding + (year - MIN_YEAR) / (MAX_YEAR - MIN_YEAR) * (bottom_line - top_line - 2 * padding)` where `top_line = 50`, `bottom_line = HEIGHT - AXIS_HEIGHT`, and `padding = 15`. Let's add MIN_YEAR and MAX_YEAR to represent that we are going from 1979 to 2019. ## Notes If needed, prior task markdown files are present in this directory like `01_scaffold.md`.

As before, give this a shot with your agents or by continuing your chat.

Draw body

Alright let's get the rest of the data visible. In 05_draw_body.md:

# Draw body Next, I would like to implement `_get_length` and support drawing the labels and bars for the other two columns in `draw`. ## Method Let's start with `_get_length`. This should determine how wide the bar should be in pixels based on if the data point is for wolves or for moose. This should be a linear scale similar to our year scale but without the extra padding. Next, let's have the count for the species drawn at the y position for year and right aligned for wolves / left aligned for moose but in the color corresponding to the series. This will create the "tornado" that makes it easier to see one species trading off for the other. This should happen 5 pixels above the y position with bottom alignment. Then, at the y position itself, we should draw the rectangle using rect mode of corner where the rectangle is 3 pixels tall. ## Notes If needed, prior task markdown files are present in this directory like `01_scaffold.md`.

Continue and we should have a pretty good visualization! However, there are a few things we might want to tighten up since we have so much control over drawing. Once more, you might be seeing some things to improve and you can ask for help like:

I think we are almost there... the text is a bit high off of the corresponding bar. Can you bring them closer together and make the text right above the bars like 2px smaller (ensure the year text still uses the original size)? Let's also shift the bar down just maybe 2 pixels.

Finishing touches

Finishing off this iterative process, let's highlight the max values from both series and clean up the title / labeling. Consider this 06_finish.md:

# Finish To finish off the visualization, we will add highlighting and clean up on spacing / labels. ## Highlighting Let's add a dictionary called `self._observed_maxes` which has entries for wolves and moose. Let's use the `max` function over a `map` to get the max observed value for each: `max(map(lambda x: int(x), map(lambda x: x['Wolves'], data)))`. Let's then, while iterating in `_draw_body` see if the value for the series matches the maximum value for the series and, if it is, let's have the fill for that text for just that data point for just that series alone be `AXIS_COLOR`. ## Spacing / labels Let's use 18px `AXIS_COLOR` text at the top of the sketch that says "Isle Royale National Park" as the title, drawn at y position 21 with center and bottom alignment. Let's also ensure the year labels are bottom aligned and 5 pixels above the y position for the year. ## Notes If needed, prior task markdown files are present in this directory like `01_scaffold.md`.

Take a look at the result and, if anything needs tweaks, be sure to use the coordinating agent to resolve anything final or use this as a chance to modify the code directly!

Reflection

This tutorial offers some techniques to engage AI on difficult or long tasks where you can break up elements into individual steps and, optionally, then ask subagents to work through those steps using repeatable structured actions all without exhausting context. In any case, this co-iteration with AI remains essential so that you can retain an active hand in ensuring a positive overall outcome, guiding assistants in their work as they go. Indeed, thanks to things like llms.txt, you can also invite assistants into tasks where they can use the latest information available.

Granted, what we discussed today may not necessarily be required for each task:

Consider these as valuable ingredients as you craft your engagement with AI. However, as with all things in engineering, you have to decide what is best for the task at hand.

Citations