TouchDesigner + Claude
Updated: 2026-05*
This article was generated by Claude and some portions are still unverified.
1. Introduction
This lecture covers how to call Claude (Anthropic’s large language model) from TouchDesigner (hereafter TD) and integrate it into your work, targeting both Windows and macOS. Claude is an AI that takes natural-language instructions and produces text generation, image understanding, structured data, and more. Calling the Claude API from TD lets you fold “semantic judgment,” “linguistic responses,” and “context-sensitive parameter generation” — things that are awkward to express in a node graph — into your projects.
We will work through the API key setup and then step through representative usage patterns: text generation, Vision (image input), and Tool use (function calling). The prerequisites are a working knowledge of basic Python syntax and familiarity with TD’s Text DAT and Web Client DAT.
1.1 Topics covered
- Obtaining an Anthropic API key and managing it safely via environment variables
- Installing the
anthropicPython SDK into a TD project - Calling the API without the SDK, using TD’s built-in Web Client DAT
- Auto-generating parameters via structured (JSON) responses
- Sending TOP images to Claude with the Vision feature
- Applications of streaming responses and Tool use
1.2 References
The content here draws from the following sites and official documentation.
References:
- Anthropic official documentation
- Anthropic Python SDK (GitHub)
- Models overview
- Vision (image input)
- Tool use
- Streaming Messages
- Python in TouchDesigner (Derivative official)
2. Development environment
To use Claude from TD you need to (a) set up access to the Anthropic API and (b) choose how to call the API from TD. The first is the same on both OSes; the second is a choice between the SDK approach and the Web Client DAT approach.
2.1 Obtaining an Anthropic API key
Using the Claude API requires an Anthropic account and an API key.
- Go to console.anthropic.com and create an account.
- Add a minimal amount of credit from “Billing” (about USD 5 is plenty for the examples in this lecture).
- Issue a new key from “API Keys.”
- Save the resulting key (the string starting with
sk-ant-).
- The API key is only displayed in full on the screen immediately after issuance — be sure to copy it then.
- Never write the API key directly into a TD project file, into a Text DAT body, or into any text under git management. If it leaks, an unrelated third party can rack up charges on your account.
2.2 Passing the key via an environment variable
The safest approach is to set the API key as an OS environment variable and read it from inside TD with os.environ.get('ANTHROPIC_API_KEY').
Windows setup
- From the Start menu, search for “environment variables” → open “Edit environment variables.”
- Add a new user environment variable:
- Name:
ANTHROPIC_API_KEY - Value: (the
sk-ant-...you obtained)
- Name:
- Restart TD so it picks up the change.
macOS setup
- In a terminal, open
~/.zshrcand append:export ANTHROPIC_API_KEY="sk-ant-..."
- Run
source ~/.zshrcto apply it. - Launch TD from that same terminal with
open -a TouchDesigner.
- On Mac, launching TD by double-clicking the icon in Finder may not inherit the environment variables. To be sure, launch it from the terminal with
open -a.
To verify the variable arrived, run this in TD’s Textport (Alt+T on Windows, ⌥+T on Mac):
import os
key = os.environ.get('ANTHROPIC_API_KEY')
print(key[:15] + '...' if key else 'not set')A prefix like sk-ant-api03-... means success. If you see not set, the variable hasn’t reached TD — restart TD or change how you launch it.
2.3 Installing the anthropic SDK
There are two ways to call the Claude API from TD.
- Method A: Use the official
anthropicPython SDK. The code is simpler and the feature set is broader. Requires an additional install. - Method B: Hit the API directly with TD’s built-in Web Client DAT. No extra installs at all.
This lecture shows Method A in the first part of Chapter 3 and Method B at the end. If you go the SDK route, install with the steps below. If you want to skip the SDK to keep environment setup minimal, you can skip this section (optional).
Windows
- Open Command Prompt and run:
cd C:\TD_Projects\my_project
"C:\Program Files\Derivative\TouchDesigner\bin\python.exe" -m pip install --target=python_libs anthropicmacOS
- In a terminal, run the following (assuming you have a Homebrew Python that matches TD’s Python version):
cd ~/TD_Projects/my_project
/opt/homebrew/bin/python3.11 -m pip install --target=python_libs anthropic- For the macOS prerequisite (installing a parallel Python via Homebrew), see Chapter 2 of the companion lecture “TouchDesigner + Python.”
- It is important that the install target is
python_libsinside the project folder. TD will not pick up a global install.
Verify the install in Textport:
import sys
sys.path.append(project.folder + '/python_libs')
import anthropic
print(anthropic.__version__)If a version number prints, you’re good.
3. Simple integration examples
Four minimum-viable examples in order. Sections 3.1–3.3 use Method A (SDK); 3.4 uses Method B (Web Client DAT).
3.1 Get a one-line response
The smallest possible example: ask Claude for a one-line reply.
- Text DAT
td_claude_hello- Open “Edit Contents…” and paste:
import sys, os
sys.path.append(project.folder + '/python_libs')
import anthropic
client = anthropic.Anthropic(api_key=os.environ.get('ANTHROPIC_API_KEY'))
msg = client.messages.create(
model='claude-haiku-4-5-20251001',
max_tokens=100,
messages=[
{'role': 'user', 'content': 'Explain TouchDesigner in one line.'}
]
)
print(msg.content[0].text)Right-click the Text DAT → Run Script. After 1–3 seconds the response text should appear in the Textport.
- We’re using
claude-haiku-4-5-20251001. Haiku is the fast, low-cost light model — plenty for everything in this lecture. If you need higher-quality responses, switch toclaude-sonnet-4-6; for top-tier quality,claude-opus-4-7.
3.2 Generate a poem and display it on a Text TOP
An example of showing the response on screen via a TOP.
Wire the nodes as follows.
- Text DAT
td_claude_poem(holds the response) - Text DAT
td_claude_runner(the script that runs) - Text TOP
text_display
Text TOP text_display settings
- Text:
op('td_claude_poem').text(Expression mode) - Font Size: 48
- Resolution: 1280 × 720
Text DAT td_claude_runner script
import sys, os
sys.path.append(project.folder + '/python_libs')
import anthropic
client = anthropic.Anthropic(api_key=os.environ.get('ANTHROPIC_API_KEY'))
msg = client.messages.create(
model='claude-haiku-4-5-20251001',
max_tokens=200,
messages=[
{'role': 'user', 'content': 'Write a three-line poem on the theme of light and shadow. Return only the poem, no preamble or commentary.'}
]
)
op('td_claude_poem').text = msg.content[0].textEach time you Run Script on td_claude_runner, a new three-line poem appears on the Text TOP.
- API calls have a 1–3 second latency, so they’re not suitable for per-frame invocation. Drive them with events — button presses, audio-level thresholds, timers — instead.
3.3 Receive parameters as JSON
Claude’s strength is not only free-form text but its ability to return structured data on demand. Here we generate multiple parameter values at once from a text description of a scene’s mood.
- Text DAT
td_claude_mood
import sys, os, json
sys.path.append(project.folder + '/python_libs')
import anthropic
client = anthropic.Anthropic(api_key=os.environ.get('ANTHROPIC_API_KEY'))
prompt = '''
Return parameters describing the mood of a scene as JSON. No preamble, no commentary, no code-fence markers.
Adhere strictly to the following schema:
{
"r": float 0.0-1.0,
"g": float 0.0-1.0,
"b": float 0.0-1.0,
"speed": float 0.0-2.0,
"noise_amount": float 0.0-1.0
}
r/g/b are the RGB values (0.0-1.0) of a representative color for the scene.
The theme this time is "an underwater cave at midnight."
'''
msg = client.messages.create(
model='claude-haiku-4-5-20251001',
max_tokens=300,
messages=[{'role': 'user', 'content': prompt}]
)
params = json.loads(msg.content[0].text)
op('constant1').par.colorr = params['r']
op('constant1').par.colorg = params['g']
op('constant1').par.colorb = params['b']
op('lfo1').par.rate = params['speed']
op('noise1').par.amp = params['noise_amount']Node setup:
- Text DAT
td_claude_mood - Constant TOP
constant1 - LFO CHOP
lfo1 - Noise TOP
noise1
Swap the theme line (“an underwater cave at midnight”) for “a park on a summer afternoon,” “the deepest corner of an abandoned factory,” “a stone garden with falling cherry blossoms,” etc. — and you’ll get parameters tuned to each mood.
- Occasionally Claude breaks the instruction and adds a preamble.
json.loadsthen raises — wrap it intry/except, or move to a larger model (sonnet or above) for more stability. - You can also have Claude return HSV and convert to RGB on your side, but for a teaching example, asking for RGB directly gives the most predictable results.
- For stricter control over structured responses, use the Tool use mechanism covered in 4.3 — it prevents schema violations at the API level.
3.4 Call the API without the SDK, via Web Client DAT
If you’d rather not install the anthropic SDK, TD’s built-in Web Client DAT can hit the API directly. Useful when you want to minimize environment setup at the distribution end, or skip Homebrew on Mac.
Wire the nodes as follows.
- Table DAT
headers(for request headers) - Web Client DAT
webclient1 - Text DAT
response(to extract the response)
Contents of Table DAT headers (a two-column table — left column is the header name, right column is the value)
- Row 1:
x-api-key| (the API key value) - Row 2:
anthropic-version|2023-06-01 - Row 3:
content-type|application/json
Web Client DAT webclient1 settings
- Request URL:
https://api.anthropic.com/v1/messages - Request Method: POST
- Request Headers DAT:
headers- The exact parameter name varies by TD version (“Headers DAT,” “Header Table,” etc.). On the Web Client DAT’s parameter page, look for the DAT parameter that references header tables and point it at
headers.
- The exact parameter name varies by TD version (“Headers DAT,” “Header Table,” etc.). On the Web Client DAT’s parameter page, look for the DAT parameter that references header tables and point it at
- Request Body: the JSON string below.
{
"model": "claude-haiku-4-5-20251001",
"max_tokens": 200,
"messages": [
{"role": "user", "content": "Describe TouchDesigner in one sentence."}
]
}Press the Web Client DAT’s Request pulse button — the request is sent and the response is stored in webclient1’s response table.
- Writing the API key directly into a Table DAT means the key gets saved into the project file. For real-world use, inject the value at startup via a script, or supply it from a separate Text DAT that reads the environment variable.
- The Web Client DAT approach has two weaknesses: streaming responses are awkward to handle, and Vision / complex Tool use require you to hand-write the body JSON. For serious use, we recommend the SDK approach.
4. Simple experiments toward real applications
Three experiments that put Claude to more active use inside TD.
4.1 Vision: send camera frames to Claude and react to its reply
Claude accepts image input. You can send Claude a webcam frame, or even the image TD is producing in real time, and get back a textual description or assessment.
Wire the nodes as follows.
- Video Device In TOP
videoin1 - Resolution TOP
resolution1 - Text DAT
td_claude_vision(analysis script) - Text DAT
caption(holds the response) - Text TOP
caption_display
Resolution TOP resolution1 settings
- Input TOP:
videoin1 - Output Resolution: 512 × 512
- Resolution: Custom Resolution
Text TOP caption_display settings
- Text:
op('caption').text(Expression mode)
Text DAT td_claude_vision script
import sys, os, base64
sys.path.append(project.folder + '/python_libs')
import anthropic
# Save the target TOP to a temp PNG and base64-encode it
top = op('resolution1')
tmp_path = project.folder + '/tmp_frame.png'
top.save(tmp_path)
with open(tmp_path, 'rb') as f:
img_b64 = base64.b64encode(f.read()).decode('utf-8')
os.remove(tmp_path) # delete the temp file once it's been read
client = anthropic.Anthropic(api_key=os.environ.get('ANTHROPIC_API_KEY'))
msg = client.messages.create(
model='claude-haiku-4-5-20251001',
max_tokens=200,
messages=[{
'role': 'user',
'content': [
{'type': 'image', 'source': {
'type': 'base64',
'media_type': 'image/png',
'data': img_b64
}},
{'type': 'text', 'text': 'Describe what you see in haiku-style, 20 characters or fewer. Return only the poem.'}
]
}]
)
op('caption').text = msg.content[0].textEach Run Script captures that instant’s camera image, has Claude render it as haiku, and shows it on the Text TOP.
TOP.save()is synchronous. Calling it every frame will tank your frame rate — drive it from a Timer CHOP at one shot every few-to-many seconds.- 512 × 512 is plenty of resolution. Higher only raises API cost; response quality barely changes.
4.2 Stream the response and draw text incrementally
Normally Claude’s response arrives when generation completes, but in streaming mode you can receive tokens as they’re generated. This lets you generate a long passage and animate it across the screen as it’s being written.
- Text DAT
story(accumulator for the response text) - Text DAT
td_claude_streamer(the script that runs) - Text TOP
story_display
Text TOP story_display settings
- Text:
op('story').text(Expression mode)
Text DAT td_claude_streamer script
import sys, os
sys.path.append(project.folder + '/python_libs')
import anthropic
client = anthropic.Anthropic(api_key=os.environ.get('ANTHROPIC_API_KEY'))
op('story').text = '' # clear before drawing
with client.messages.stream(
model='claude-haiku-4-5-20251001',
max_tokens=500,
messages=[
{'role': 'user', 'content': 'Write a short ghost story set at an unmanned train station, about 200 characters. Return only the prose.'}
]
) as stream:
for chunk in stream.text_stream:
op('story').text = op('story').text + chunkWhen you run it, the text held in story grows in step with Claude’s generation rate. Because the Text TOP references story, the on-screen text fills in gradually.
- This blocks TD’s main thread, so TD’s overall frame rate dips while streaming. For real use, see the official Python Threading docs and run it on a separate thread.
4.3 Tool use: let Claude operate TD operators
Tool use is a mechanism where you give Claude “a list of functions you can call,” and Claude calls them. By wiring this up, you can have Claude operate TD operators directly from a natural-language instruction.
- Text DAT
td_claude_tooluse
import sys, os
sys.path.append(project.folder + '/python_libs')
import anthropic
client = anthropic.Anthropic(api_key=os.environ.get('ANTHROPIC_API_KEY'))
tools = [{
'name': 'set_constant_color',
'description': "Change the color (RGB) of the scene's central Constant TOP",
'input_schema': {
'type': 'object',
'properties': {
'r': {'type': 'number', 'description': 'Red component, 0.0 to 1.0'},
'g': {'type': 'number', 'description': 'Green component, 0.0 to 1.0'},
'b': {'type': 'number', 'description': 'Blue component, 0.0 to 1.0'}
},
'required': ['r', 'g', 'b']
}
}, {
'name': 'set_noise_amount',
'description': "Change the overall noise amount of the scene",
'input_schema': {
'type': 'object',
'properties': {
'amount': {'type': 'number', 'description': '0.0 (calm) to 1.0 (maximally agitated)'}
},
'required': ['amount']
}
}]
user_prompt = "Make it sunset-colored, and the air a little grainy please."
msg = client.messages.create(
model='claude-sonnet-4-6',
max_tokens=500,
tools=tools,
messages=[{'role': 'user', 'content': user_prompt}]
)
for block in msg.content:
if block.type == 'tool_use':
args = block.input
if block.name == 'set_constant_color':
op('constant1').par.colorr = args['r']
op('constant1').par.colorg = args['g']
op('constant1').par.colorb = args['b']
elif block.name == 'set_noise_amount':
op('noise1').par.amp = args['amount']Give the vague instruction “Make it sunset-colored, and the air a little grainy please.” and Claude responds with calls like set_constant_color(r=0.95, g=0.45, b=0.15) and set_noise_amount(amount=0.25), which the script then applies to the real parameters.
Add any functions you like to the tools array — set_lfo_rate, set_camera_zoom, play_audio_clip — and you have Claude as a “natural-language-driven performance operator.”
- Tool use depends on the model’s reasoning ability. Haiku basically works, but picking the right tool from several based on context is more stable with sonnet or above.
- Compared to the JSON-prompt approach in 3.3, Tool use enforces the schema at the Anthropic end, giving you much higher reliability on structured output.
5. Three fun ideas
5.1 Idea 1: The AI audience — Claude watching and critiquing your work
Use Vision to send TD’s own output (a Render TOP or a Window COMP capture) to Claude at a fixed interval. Set the prompt to “You are a contemporary art critic. Describe in two sentences your impression of the video work you are now seeing,” and you have an installation where Claude critiques your own TD piece, continuously.
- Render TOP
render1(or whichever TOP is your final output) - Timer CHOP
timer1(30-second period) - CHOP Execute DAT
dat_exec1(callback ontimer1’s Cycle) - Text DAT
critic_log(accumulator for the critiques) - Text TOP
critic_display(overlaid at the bottom of the screen)
In the CHOP Execute DAT’s onValueChange or onOffToOn, send the current frame of render1 to Claude and append the response to critic_log. Reuse the script from 4.1 and swap the text part for:
'You are a contemporary art critic. Describe in two sentences, with technical vocabulary, your impression of the video work you are now seeing.'Accumulate responses into critic_log as a ring buffer, and the act of Claude’s commentary slowly piling up itself becomes part of the exhibit. Export as CSV after the show and you have a written record: “this work, as seen by Claude” (a nice extra).
5.2 Idea 2: A self-evolving scene — Claude designs the next 30 seconds
An extension of the JSON parameter generation in 3.3. Give Claude “the parameters of the previous scene” and “how long the current audience member has been standing here,” and ask how the next 30 seconds should be staged.
- Timer CHOP
scene_timer(fires on a 30-second period) - Text DAT
td_claude_director(the directing script) - Animation COMP
anim1(interpolation target for the returned parameters)
The prompt for td_claude_director (sketch only — previous_params is a dict holding the parameters of the previous 30 seconds, dwell_time is the audience dwell time in seconds from a Timer CHOP or sensor, and schema is the JSON schema string reused from 3.3):
prompt = f'''
You are the director of a video installation.
The parameters of the previous 30 seconds were:
{json.dumps(previous_params, ensure_ascii=False)}
The audience member has now been standing in front of this work for {dwell_time} seconds.
For the next 30 seconds, in order not to bore the audience and not to overstimulate them, return the next scene's parameters as JSON conforming to the schema below. No commentary:
{schema}
'''Feed the returned parameters into an Animation COMP as the interpolation target, and Claude becomes a scene that continually designs itself forward. You can write context-aware directing logic in natural language: ramp up intensity for an audience that lingers, calm things back down for the next person if someone leaves quickly (a nice extra).
- At one call every 30 seconds (
claude-haiku-4-5, roughly a few hundred to a thousand tokens input + output), the hourly cost is on the order of tens of US cents — varies with token count and model pricing.
5.3 Idea 3: Get Claude as a sounding board for your in-progress network
Finally, a use case for production support rather than the audience experience. Collect the composition of your currently open TD network (operator names, types, connections, key parameters) in Python and ask Claude “how can this network be made more interesting?”
- Text DAT
td_claude_advisor
import sys, os, json
sys.path.append(project.folder + '/python_libs')
import anthropic
# Collect info on operators directly under the parent COMP of this Text DAT.
# To target all of /project1, replace parent() with op('/project1').
ops_info = []
for c in parent().findChildren(depth=1):
ops_info.append({
'name': c.name,
'type': c.OPType,
'inputs': [i.name for i in c.inputs if i is not None]
})
client = anthropic.Anthropic(api_key=os.environ.get('ANTHROPIC_API_KEY'))
msg = client.messages.create(
model='claude-sonnet-4-6',
max_tokens=1000,
messages=[{
'role': 'user',
'content': f'''My TouchDesigner network currently in progress is as follows:
{json.dumps(ops_info, ensure_ascii=False, indent=2)}
Propose three changes that would make this network more interesting. For each, state briefly:
- Which operators to add or modify
- Why the change would make the expression more interesting.'''
}]
)
print(msg.content[0].text)Run it and three change proposals appear in the Textport. Pipe it through a Slack webhook and you have a partner that lives in your studio while you work. It’s a way out of the “I keep reaching for the same operators and making the same-looking output” cul-de-sac.
- Make the network-info collection recursive and you can read inside child COMPs as well, at the cost of more tokens. Start with
depth=1.
6. Summary
The key points for using Claude from TD.
Recommended models by use case:
- Short responses, JSON parameter generation, haiku / short text:
claude-haiku-4-5-20251001 - Tool use, structural judgment, Vision responses:
claude-sonnet-4-6 - Critical performance transitions, long-form generation:
claude-opus-4-7
Unlike nodes or CHOPs, Claude (at least at present) is not something you can “call every frame” — both latency and cost assume event-driven invocation, somewhere between every few seconds and every few tens of seconds. So at the design stage of your work, “when to ask Claude” and “what to ask” need to be folded into the scene design itself.
A workable order of study:
- 3.1–3.2: experience “call it and receive a response.”
- 3.3: get comfortable with parameter generation via JSON responses.
- 4.1: experience image input with Vision.
- 4.3: treat Claude as an active operator via Tool use.
If you fold Claude in as “a second director,” “the first audience,” or “a sounding board while making,” you’ll widen the semantic and linguistic range of expression — territory that a TD node graph alone can’t easily reach.
