Creative RAG (local)
Status: experimental, embarcado. O MVP do Creative RAG entrou em
mainvia PR #75 / commit0956eea, e a onda de robustez pós-MVP entrou via PR #76 (novas fontes vivas, batch de embeddings, backend opcional LanceDB, migração de índice legado). O feature é opt-in e desligado por padrão (TDMCP_RAG_ENABLED=0). Quando desligado, o tdmcp se comporta exatamente como antes: o serviço não é construído, nenhum recursotdmcp://creative/*é registrado, e o subcomandocreative-ragimprime uma mensagem de "desativado" e sai com código 0.
Em 60 segundos
Pré-requisito: Ollama instalado localmente.
# 1. Suba o servidor de embeddings local e baixe o modelo padrão.
ollama serve &
ollama pull nomic-embed-text
# 2. Habilite o feature.
export TDMCP_RAG_ENABLED=1
# 3. Puxe cards das fontes vivas para .tdmcp/creative-rag/cards/
tdmcp creative-rag sync
# 4. Embede cada card via Ollama em .tdmcp/creative-rag/index.jsonl
tdmcp creative-rag index
# 5. Busque.
tdmcp creative-rag search "neon city"Rode sync de novo para atualizar cards upstream; rode index em seguida para re-embedar somente os cards que mudaram.
O que é
Creative RAG é um repertório criativo local: uma biblioteca pequena e versionada de cards descrevendo obras, artistas, projetos e técnicas de licença aberta, indexada para que o modelo (e você) possa pesquisar por inspiração — "me mostra referências de movimento cinético, alto contraste e monocromático", "que ilustrações botânicas em domínio público podem semear um sistema de crescimento". Cada resultado carrega sourceUrl, license e rightsNotes, então atribuição e limites de reuso viajam junto com a referência.
É deliberadamente estreito:
- Repertório, não policy. É repertório contextual, não um motor de decisão. Nunca decide o que é seguro executar. Espelha o limite descrito no AI Party LLM Training Plan: o runtime de policy (
ShowIntentSchema/showDirectorRuntime) continua sendo a única autoridade de segurança. O Creative RAG só fornece contexto criativo — climas, paletas, linguagem de movimento, nomes de técnicas e nomes de tools tdmcp já existentes que poderiam realizar uma estética. - Não é fine-tuning. Nenhum peso de modelo é alterado. Sem treinamento. É retrieval sobre um índice JSONL local.
- Não é o
src/knowledge. A knowledge base de operadores/Python/padrões comitada no repositório continua sendo a fonte de verdade sobre como o TouchDesigner funciona. O Creative RAG é uma biblioteca separada, cultivada pelo usuário, do que fazer.
Limite duro — sem hardware, sem DMX, sem exec
Os caminhos de sync/search/resource não tocam a bridge do TouchDesigner, DMX, fixtures, nem executam Python. O Creative RAG em si é um subcomando de CLI mais recursos MCP read-only. O handoff opcional para execução é uma tool MCP separada e explicitamente gated, apply_creative_card, habilitada somente com TDMCP_RAG_APPLY_CARD=1; ela roteia para uma allowlist hardcoded de builders Layer 1, valida os argumentos do alvo e suporta dry_run antes de invocar o TouchDesigner. As únicas chamadas de rede em sync/search são:
- as APIs HTTP das fontes upstream (as fontes vivas de museus/arquivos), durante um
creative-rag syncexplícito; e - o endpoint local de embeddings do Ollama, durante
creative-rag indexecreative-rag search(a query é embedada antes do ranqueamento).
Ambas são locais/opt-in e isoladas: falha do Ollama ou de rede vira erro tipado e nunca derruba outras tools nem o servidor.
Fontes inclusas (vivas)
Sete fontes abertas, todas com sinal de licença por item. Quatro são keyless; duas dependem de chave de API lida diretamente do ambiente pelo adaptador (nunca propagada via CreativeRagConfig, nunca logada) — sem a chave, o adaptador loga uma linha clara de skip e devolve nada, então um sync sobre todas as fontes ainda termina com sucesso.
| Fonte | API base | Chave | Sinal de licença | Notas |
|---|---|---|---|---|
| Art Institute of Chicago | https://api.artic.edu/api/v1 | (keyless) | is_public_domain (boolean) ⇒ PublicDomain, senão Unknown | URL de imagem IIIF montada a partir de config.iiif_url + image_id. |
| The Met | https://collectionapi.metmuseum.org/public/collection/v1 | (keyless) | isPublicDomain (boolean) ⇒ PublicDomain / CC0, senão Unknown | Dois passos: search → objects/{id}. |
| Rijksmuseum | https://data.rijksmuseum.nl | (keyless) | Rights statement Linked-Art ⇒ CC0 / PublicDomain / Unknown | Dois passos: search/collection (OrderedCollectionPage) → resolve cada id como Linked-Art JSON; imagem via shows → VisualItem → DigitalObject → access_point. |
| Cleveland Museum of Art | https://openaccess-api.clevelandart.org/api/artworks | (keyless) | share_license_status ("CC0" ⇒ CC0), senão Unknown | Chamada única; imagem em images.web.url. |
| Smithsonian Open Access | https://api.si.edu/openaccess/api/v1.0/search | TDMCP_RAG_SMITHSONIAN_KEY | media.usage.access == "CC0" ⇒ CC0, senão Unknown | Search único (q="online_media_type:\"Images\" AND media_usage:CC0"). Verificado em sync real. |
| Wikimedia Commons | https://commons.wikimedia.org/w/api.php | (keyless) | Código extmetadata.License: cc0⇒CC0; pd/public⇒PublicDomain; cc-by-sa*⇒CC-BY-SA; cc-by*⇒CC-BY; senão Unknown | Chamada única via generator=categorymembers sobre Category:CC-Zero + imageinfo. Verificado em sync real. |
| Europeana | https://api.europeana.eu/record/v2/search.json | TDMCP_RAG_EUROPEANA_KEY | rights[0] (URI CC/RS) classificado pelo mesmo classificador CC/RS do Rijksmuseum | Verificado contra sync real com chave. O guid carrega a wskey na querystring — ela é removida antes de persistir, então o sourceUrl/id salvo nunca contém a chave. |
O sync puxa um número limitado de itens por fonte (padrão 10, configurável por execução via
--limit) para o MVP rodar rápido e ser educado com as APIs upstream. Não é mirror completo.
Fontes planejadas (stubs)
Seis outras fontes estão escopadas mas não implementadas. Elas existem como stubs documentados com status: "planned" e um motivo explícito, para que o time e os usuários saibam por que cada uma foi adiada. Nenhuma está ligada ao sync.
| Fonte | Motivo do adiamento |
|---|---|
| Harvard Art Museums | Requer chave de API (auth). |
| Cooper Hewitt | Requer chave de API (auth). |
| Internet Archive | Licenciamento misto/ambíguo por item; exigiria scraping de metadata de direitos. |
| WikiArt | Sem API aberta oficial; exigiria scraping e as licenças são restritas. |
| Behance / Vimeo / portfólios de artistas | Sem licença aberta; copyrighted (restrito) — referência só, nunca ingerir binários. |
| Shadertoy | Licenciamento varia por shader e frequentemente é não especificado (ambíguo); coberto melhor pelos imports ISF/Shadertoy já existentes do tdmcp. |
License policy (codificada, não decidida em runtime)
A policy é função pura da license do card, decidida na hora do sync — sem prompt nem override em runtime:
- Um binário (imagem) só é baixado/armazenado se a
licensedo card estiver emTDMCP_RAG_LICENSE_ALLOWLIST(padrãoCC0,PublicDomain). - Fonte que não dá sinal de licença ⇒ a
licensedo card vira"Unknown"e nenhum binário é baixado. O card continua existindo (texto +sourceUrl) e é pesquisável como referência. - Card que desaparece em re-sync (upstream devolve 404 / sumiu) é tombstonado (
tombstone: true, binário removido), nunca apagado em silêncio — então uma referência removida é auditável e não some.
A extensão do binário também é decidida automaticamente pelo Content-Type da resposta (image/png ⇒ .png, image/webp ⇒ .webp, image/gif ⇒ .gif, image/svg+xml ⇒ .svg, image/tiff ⇒ .tiff; padrão .jpg) — não é hardcoded .jpg.
Valores de license: CC0 · PublicDomain · CC-BY · CC-BY-SA · Unknown · Restricted.
Configuração
Variáveis de ambiente com lastro em config são opt-in e parseadas/validadas em src/utils/config.ts (Zod). As chaves de API do Smithsonian e Europeana são exceção: são lidas direto do ambiente pelos adaptadores das fontes (nunca propagadas via CreativeRagConfig), então não passam pelo schema do config nem aparecem em log. Env vars seguem a convenção TDMCP_*.
| Env var | Config key | Padrão | Notas |
|---|---|---|---|
TDMCP_RAG_ENABLED | ragEnabled | 0 (false) | Chave mestra. Quando 0, sem resource, sem injeção de contexto, subcomando vira no-op com mensagem. |
TDMCP_RAG_DATA_DIR | ragDataDir | .tdmcp/creative-rag | Cards, binários e índice vivem aqui. Gitignored. |
TDMCP_RAG_OLLAMA_URL | ragOllamaUrl | http://127.0.0.1:11434 | Endpoint local de embeddings. |
TDMCP_RAG_EMBED_MODEL | ragEmbedModel | nomic-embed-text | Precisa estar baixado (ollama pull nomic-embed-text). Bibliotecas multilíngues. O padrão nomic-embed-text é voltado ao inglês. Para repertórios em português, espanhol, francês ou mistos, defina TDMCP_RAG_EMBED_MODEL=bge-m3 e rode ollama pull bge-m3, depois tdmcp creative-rag index --rebuild para forçar todos os cards vivos pelo novo modelo. Alternativa: mxbai-embed-large. |
TDMCP_RAG_LICENSE_ALLOWLIST | ragLicenseAllowlist | CC0,PublicDomain | CSV; licenças para as quais binários podem ser armazenados. |
TDMCP_RAG_EMBED_BATCH | ragEmbedBatch | 64 | Inputs por POST de embed do Ollama. O index parte o conjunto de cards em batches desse tamanho; a guarda de "um vetor por input" dispara por batch. Faixa 1–512. |
TDMCP_RAG_BACKEND | ragBackend | jsonl | Backend de índice. jsonl é o store in-memory full-load. lancedb é um caminho de escala experimental que usa a dependência opcional @lancedb/lancedb. |
TDMCP_RAG_SMITHSONIAN_KEY | (lida na fonte) | (não setada) | Chave de API do Smithsonian. Lida direto do ambiente pelo adaptador (nunca passa pelo config nem é logada); sem chave ⇒ a fonte é pulada. |
TDMCP_RAG_EUROPEANA_KEY | (lida na fonte) | (não setada) | Chave de API do Europeana. Lida direto do ambiente pelo adaptador (nunca passa pelo config nem é logada); sem chave ⇒ a fonte é pulada. |
Backend LanceDB (experimental, dependência opcional)
TDMCP_RAG_BACKEND=lancedb seleciona um store de índice apoiado em LanceDB em vez do JSONL padrão. Requer a dependência opcional@lancedb/lancedb, declarada como peerDependency opcional e portanto não instalada por um npm install padrão. Para usar, instale explicitamente:
npm install @lancedb/lancedbSe a dependência opcional estiver ausente (ou se o primeiro acesso à tabela falhar), a factory do store loga um warning claro e cai de volta para o backend JSONL — então um mau-config de lancedb nunca quebra sync/index. Os scores de busca são recalculados com cosseno sobre a janela de candidatos ANN, então são byte-a-byte comparáveis com o backend JSONL.
Uso da CLI
# 1. Puxa cards das fontes vivas para TDMCP_RAG_DATA_DIR/cards/ como Markdown
# + frontmatter YAML. Honra a license policy (binários só para licenças
# da allowlist; sem licença ⇒ Unknown, sem binário; 404 ⇒ tombstone).
tdmcp creative-rag sync [--source artic --source rijksmuseum --source met] [--limit 10]
# 2. Embede cada card via Ollama POST /api/embed e escreve o índice JSONL.
# Cacheado por contentHash + embeddingModel, então re-rodar só embeda
# cards novos/alterados. Os inputs são enviados em batches de
# TDMCP_RAG_EMBED_BATCH (padrão 64).
tdmcp creative-rag index
# 3. Cosine search sobre o índice local, top-k, com filtros opcionais.
tdmcp creative-rag search "kinetic monochrome motion" --k 5 --license CC0,PublicDomain
tdmcp creative-rag search "botanical growth" --k 8 --type artwork --tags nature,lineQuando TDMCP_RAG_ENABLED=0, todo subcomando imprime uma linha "Creative RAG is disabled (set TDMCP_RAG_ENABLED=1)" e sai com 0 — nunca erro.
MCP resources (read-only)
Registrados somente quando TDMCP_RAG_ENABLED=1:
tdmcp://creative/cards/{id}— um card como JSON (frontmatter completo; sempre incluisourceUrl,license,rightsNotes).{id}é o id do card.tdmcp://creative/search?q=...— busca read-only; devolve top-k cards comsourceUrl/license/rightsNotesem cada item. Aceita os query paramsq,k,license,type,tags.
Ambos são read-only: ler um resource nunca muta estado, nunca chama a bridge, nunca roda Python.
De um card para uma rede TouchDesigner
A busca entrega referências; ela não executa nada. Para transformar um card em uma rede TD, habilite o gate explícito de apply e faça preview do plano antes:
export TDMCP_RAG_ENABLED=1
export TDMCP_RAG_APPLY_CARD=1Depois chame a tool MCP apply_creative_card com o id do card:
{
"card_id": "<sha256 card id>",
"affordance_index": 0,
"dry_run": true
}O dry run devolve {tool, args} depois de aplicar os defaults Zod da tool alvo. Só então rode com dry_run: false. Se você passar um override que não é um argumento real da tool alvo, a chamada falha antes de qualquer builder Layer 1 rodar.
Formato do card
Cards são arquivos Markdown com frontmatter YAML em TDMCP_RAG_DATA_DIR/cards/<id>.md, validados por um schema Zod (schemaVersion: 1).
---
schemaVersion: 1
id: "<sha256 of sourceUrl>"
type: artwork # project | artist | artwork | technique | cue_reference
title: "Composition VIII"
artist: "Wassily Kandinsky"
sourceUrl: "https://www.artic.edu/artworks/123"
sourceName: "Art Institute of Chicago"
license: PublicDomain # CC0 | PublicDomain | CC-BY | CC-BY-SA | Unknown | Restricted
rightsNotes: "Public domain per source is_public_domain flag."
year: 1923
medium: "Oil on canvas"
tools: [] # ferramentas/mídias usadas na obra original
tags: ["geometric", "high-contrast", "kinetic"]
visualLanguage: "hard-edged geometry, primary colors on white"
motionLanguage: "implied rotational motion"
interaction: null
materials: "oil"
lighting: null
palette: ["#e4332a", "#1f4fa6", "#f2c200"]
tdmcpAffordances: ["create_generative_art", "create_kaleidoscope"]
contentHash: "<sha256 of the canonical card text>"
embeddingModel: "nomic-embed-text" # setado pelo `index`
# embedding vive no índice JSONL, não no arquivo do card
tombstone: false
---
Corpo livre: uma nota curta sobre por que essa referência é útil.tdmcpAffordances lista dicas de execução para tools Layer 1 existentes. Cards sincronizados de fontes vivas inferem uma lista curta e allowlisted, como create_generative_art, create_kaleidoscope, create_feedback_network, create_growth_system e create_color_grade. São dicas, não ações — ler um card nunca invoca nada.
Formato do índice JSONL
TDMCP_RAG_DATA_DIR/index.jsonl (gitignored). O arquivo agora tem um wrapper de indexVersion na primeira linha (envelope versionado), seguido por um objeto JSON por linha, um por card embedado:
{"id":"<sha256>","contentHash":"<sha256>","embeddingModel":"nomic-embed-text","embedding":[0.0123,-0.045],"title":"...","type":"artwork","license":"PublicDomain","tags":["geometric"],"sourceUrl":"...","sourceName":"..."}Linhas no formato legado (sem o envelope indexVersion) são migradas em leitura em vez de descartadas, então um JSONL antigo continua usável depois de upgrade.
Search carrega o JSONL em memória, calcula similaridade cosseno entre o embedding da query e cada linha, aplica filtros license/type/tags e devolve top-k. Para o tamanho de corpus do MVP (centenas de cards), o cosseno em memória é instantâneo e sem dependências.
Fluxo de embedding via Ollama
index lê cada card, calcula seu contentHash e pula qualquer card já embedado com o mesmo contentHash + embeddingModel (cache). Para os demais, parte o conjunto em batches de TDMCP_RAG_EMBED_BATCH cards e chama POST {ragOllamaUrl}/api/embed com { "model": ragEmbedModel, "input": ["<card text>", ...] }, lendo { "embeddings": [[...]] } (a forma legada single { "embedding": [...] } também é aceita). A guarda de "um vetor por input" dispara por batch, então respostas malformadas falham cedo. Falhas levantam OllamaConnectionError / OllamaTimeoutError / OllamaApiError tipados (espelhando src/td-client/types.ts); a CLI reporta limpo e o servidor não é afetado.
Solução de problemas
- Ollama offline /
ECONNREFUSED 127.0.0.1:11434. Suba o daemon:ollama serve &. AponteTDMCP_RAG_OLLAMA_URLpara outro host/porta se for o caso. - Modelo não baixado. O Ollama devolve "model not found" no primeiro
index/search. Rodeollama pull $TDMCP_RAG_EMBED_MODEL(padrãonomic-embed-text). - Allowlist de fontes vazia / nada sincronizado. O
syncaceita--sourcee--limit. Sem--source, todas as fontes vivas rodam com o teto padrão por execução. Fontes key-gated (Smithsonian, Europeana) são silenciosamente puladas quando aTDMCP_RAG_*_KEYcorrespondente não está setada — a linha de log diz isso, mas osyncretorna sucesso. - Data dir não gravável.
TDMCP_RAG_DATA_DIR(padrão.tdmcp/creative-rag) precisa ser gravável. Erros de permissão sobem como erros tipados emsync/index. - Tombstone inesperado. Um card só é tombstonado quando a fonte dele rodou com sucesso nessa execução e não reemitiu o id. Se aparecer um sem razão aparente, o upstream removeu (ou relicenciou) o item. Rodar
syncde novo pega o estado mais recente; o tombstone é histórico auditável, não perda de dados. - LanceDB avisou e caiu de volta para JSONL.
TDMCP_RAG_BACKEND=lancedbexige a dependência opcional@lancedb/lancedb. Instale explicitamente (npm install @lancedb/lancedb) ou aceite o backend JSONL padrão.
Limites
- LanceDB é experimental e opt-in. O backend padrão é cosseno em memória sobre JSONL, sem dep nativa.
TDMCP_RAG_BACKEND=lancedbhabilita o store LanceDB via a dependência opcional@lancedb/lancedb(fora do install padrão; cai de volta para JSONL quando ausente) — veja a seção do backend LanceDB acima. - Sete fontes vivas. Quatro APIs de museu keyless mais Smithsonian, Wikimedia Commons e Europeana (todas verificadas em sync real). Seis outras continuam como stubs planejados (acima).
- Sync limitado. Não é mirror completo; teto por execução.
- Troca para multilíngue.
nomic-embed-text(padrão) é voltado ao inglês. Para indexar repertórios em português, espanhol, francês ou mistos, definaTDMCP_RAG_EMBED_MODEL=bge-m3(ou o maiormxbai-embed-large) e rodeollama pull bge-m3. Depois reconstrua o índice:tdmcp creative-rag index --rebuild. Trocar de modelo invalida linhas de índice existentes (o campoembeddingModelarmazenado não corresponde mais ao modelo em uso);--rebuildre-embeda tudo com o novo modelo. Veja a tabela de env acima para referência completa de configuração. - Sem tools de escrita. Resource read-only + CLI apenas.
Veja o roadmap para próximos passos.