Skip to content

Creative RAG (local)

Status: experimental, embarcado. O MVP do Creative RAG entrou em main via PR #75 / commit 0956eea, 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 recurso tdmcp://creative/* é registrado, e o subcomando creative-rag imprime uma mensagem de "desativado" e sai com código 0.

Em 60 segundos

Pré-requisito: Ollama instalado localmente.

bash
# 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:

  1. as APIs HTTP das fontes upstream (as fontes vivas de museus/arquivos), durante um creative-rag sync explícito; e
  2. o endpoint local de embeddings do Ollama, durante creative-rag indexe creative-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.

FonteAPI baseChaveSinal de licençaNotas
Art Institute of Chicagohttps://api.artic.edu/api/v1(keyless)is_public_domain (boolean) ⇒ PublicDomain, senão UnknownURL de imagem IIIF montada a partir de config.iiif_url + image_id.
The Methttps://collectionapi.metmuseum.org/public/collection/v1(keyless)isPublicDomain (boolean) ⇒ PublicDomain / CC0, senão UnknownDois passos: searchobjects/{id}.
Rijksmuseumhttps://data.rijksmuseum.nl(keyless)Rights statement Linked-Art ⇒ CC0 / PublicDomain / UnknownDois passos: search/collection (OrderedCollectionPage) → resolve cada id como Linked-Art JSON; imagem via shows → VisualItem → DigitalObject → access_point.
Cleveland Museum of Arthttps://openaccess-api.clevelandart.org/api/artworks(keyless)share_license_status ("CC0"CC0), senão UnknownChamada única; imagem em images.web.url.
Smithsonian Open Accesshttps://api.si.edu/openaccess/api/v1.0/searchTDMCP_RAG_SMITHSONIAN_KEYmedia.usage.access == "CC0"CC0, senão UnknownSearch único (q="online_media_type:\"Images\" AND media_usage:CC0"). Verificado em sync real.
Wikimedia Commonshttps://commons.wikimedia.org/w/api.php(keyless)Código extmetadata.License: cc0CC0; pd/publicPublicDomain; cc-by-sa*CC-BY-SA; cc-by*CC-BY; senão UnknownChamada única via generator=categorymembers sobre Category:CC-Zero + imageinfo. Verificado em sync real.
Europeanahttps://api.europeana.eu/record/v2/search.jsonTDMCP_RAG_EUROPEANA_KEYrights[0] (URI CC/RS) classificado pelo mesmo classificador CC/RS do RijksmuseumVerificado 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.

FonteMotivo do adiamento
Harvard Art MuseumsRequer chave de API (auth).
Cooper HewittRequer chave de API (auth).
Internet ArchiveLicenciamento misto/ambíguo por item; exigiria scraping de metadata de direitos.
WikiArtSem API aberta oficial; exigiria scraping e as licenças são restritas.
Behance / Vimeo / portfólios de artistasSem licença aberta; copyrighted (restrito) — referência só, nunca ingerir binários.
ShadertoyLicenciamento 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) é baixado/armazenado se a license do card estiver em TDMCP_RAG_LICENSE_ALLOWLIST (padrão CC0,PublicDomain).
  • Fonte que não dá sinal de licença ⇒ a license do 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 varConfig keyPadrãoNotas
TDMCP_RAG_ENABLEDragEnabled0 (false)Chave mestra. Quando 0, sem resource, sem injeção de contexto, subcomando vira no-op com mensagem.
TDMCP_RAG_DATA_DIRragDataDir.tdmcp/creative-ragCards, binários e índice vivem aqui. Gitignored.
TDMCP_RAG_OLLAMA_URLragOllamaUrlhttp://127.0.0.1:11434Endpoint local de embeddings.
TDMCP_RAG_EMBED_MODELragEmbedModelnomic-embed-textPrecisa 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_ALLOWLISTragLicenseAllowlistCC0,PublicDomainCSV; licenças para as quais binários podem ser armazenados.
TDMCP_RAG_EMBED_BATCHragEmbedBatch64Inputs 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_BACKENDragBackendjsonlBackend 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:

bash
npm install @lancedb/lancedb

Se 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

bash
# 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,line

Quando 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 inclui sourceUrl, license, rightsNotes). {id} é o id do card.
  • tdmcp://creative/search?q=... — busca read-only; devolve top-k cards com sourceUrl/license/rightsNotes em cada item. Aceita os query params q, 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:

bash
export TDMCP_RAG_ENABLED=1
export TDMCP_RAG_APPLY_CARD=1

Depois chame a tool MCP apply_creative_card com o id do card:

json
{
  "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).

yaml
---
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:

json
{"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 &. Aponte TDMCP_RAG_OLLAMA_URL para outro host/porta se for o caso.
  • Modelo não baixado. O Ollama devolve "model not found" no primeiro index/search. Rode ollama pull $TDMCP_RAG_EMBED_MODEL (padrão nomic-embed-text).
  • Allowlist de fontes vazia / nada sincronizado. O sync aceita --source e --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 a TDMCP_RAG_*_KEY correspondente não está setada — a linha de log diz isso, mas o sync retorna 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 em sync/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 sync de 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=lancedb exige 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=lancedb habilita 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, defina TDMCP_RAG_EMBED_MODEL=bge-m3 (ou o maior mxbai-embed-large) e rode ollama pull bge-m3. Depois reconstrua o índice: tdmcp creative-rag index --rebuild. Trocar de modelo invalida linhas de índice existentes (o campo embeddingModel armazenado não corresponde mais ao modelo em uso); --rebuild re-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.