Loading...
Loading...
This skill should be used when building data processing pipelines with CocoIndex, a Python library for incremental data transformation. Use when the task involves processing files/data into databases, creating vector embeddings, building knowledge graphs, ETL workflows, or any data pipeline requiring automatic change detection and incremental updates. CocoIndex is Python-native (supports any Python types), has no DSL, and uses version 1.0.0 or later.
npx skill4agent add cocoindex-io/cocoindex cocoindexTargetState = Transform(SourceState)cocoindex init my-project
cd my-projectmain.pypyproject.tomlREADME.mdmain.pybuilder.settings.db_path = pathlib.Path("./cocoindex.db")# For vector embeddings with PostgreSQL
dependencies = ["cocoindex>=1.0.0", "sentence-transformers", "asyncpg"]
# For LLM extraction
dependencies = ["cocoindex>=1.0.0", "litellm", "instructor", "pydantic>=2.0"]uv run cocoindex update main.py # or: pip install -e . && cocoindex update main.pyimport cocoindex as coco
@coco.fn
async def app_main(sourcedir: pathlib.Path) -> None:
...
app = coco.App(
coco.AppConfig(name="MyApp"),
app_main,
sourcedir=pathlib.Path("./data"),
)@coco.fn@coco.fnmemo=True@coco.fn(memo=True)
async def expensive_operation(data: str) -> Result:
# LLM call, embedding generation, heavy computation
return await expensive_transform(data)memo=Trueversion=1batching=Truerunner=coco.GPUmount_each()mount()# One component per item (preferred for lists)
await coco.mount_each(process_file, files.items(), target_table)
# Single component (subpath auto-derived from fn.__name__)
await coco.mount(setup_fn, arg1)
# Dependent component (blocks until result returned)
result = await coco.use_mount(init_fn)
# Explicit subpath (when you need a specific path, e.g. in loops)
await coco.mount(coco.component_subpath("item", item_id), process_item, item)asyncmount()use_mount()mount_each()fn.__name__use_mount()# Database row target
table.declare_row(row=MyRecord(id=1, name="example"))
# File target
localfs.declare_file(outdir / "output.txt", content, create_parent_dirs=True)
# Kafka message target
topic_target.declare_target_state(key="msg-1", value=json.dumps(data))ContextKeyEMBEDDER = coco.ContextKey[SentenceTransformerEmbedder]("embedder")
@coco.lifespan
async def coco_lifespan(builder: coco.EnvironmentBuilder) -> AsyncIterator[None]:
builder.provide(EMBEDDER, SentenceTransformerEmbedder("all-MiniLM-L6-v2"))
yield
# In processing functions:
embedder = coco.use_context(EMBEDDER)@coco.lifespanContextKeykeyfrom cocoindex.resources.id import generate_id, IdGenerator
# Deterministic: same dep -> same ID
chunk_id = await generate_id(chunk.text)
# Always distinct: each call -> new ID, even with same dep
id_gen = IdGenerator()
for chunk in chunks:
chunk_id = await id_gen.next_id(chunk.text)app.update()# Enable live mode
app.update_blocking(live=True)
# Or: cocoindex update main.py -LLiveMapViewlocalfs.walk_dir(..., live=True)LiveMapFeedkafka.topic_as_map()mount_each()# LocalFS with live watching
files = localfs.walk_dir(sourcedir, live=True, ...)
await coco.mount_each(process_file, files.items(), target)
# Kafka -- inherently live
items = kafka.topic_as_map(consumer, ["my-topic"])
await coco.mount_each(process_message, items, target)import pathlib
import cocoindex as coco
from cocoindex.connectors import localfs
from cocoindex.resources.file import FileLike, PatternFilePathMatcher
@coco.fn(memo=True)
async def process_file(file: FileLike, outdir: pathlib.Path) -> None:
content = await file.read_text()
transformed = transform_content(content)
outname = file.file_path.path.stem + ".out"
localfs.declare_file(outdir / outname, transformed, create_parent_dirs=True)
@coco.fn
async def app_main(sourcedir: pathlib.Path, outdir: pathlib.Path) -> None:
files = localfs.walk_dir(
sourcedir,
recursive=True,
path_matcher=PatternFilePathMatcher(included_patterns=["**/*.md"]),
)
await coco.mount_each(process_file, files.items(), outdir)
app = coco.App(
coco.AppConfig(name="Transform"),
app_main,
sourcedir=pathlib.Path("./data"),
outdir=pathlib.Path("./out"),
)import pathlib
from dataclasses import dataclass
from typing import AsyncIterator, Annotated
import asyncpg
from numpy.typing import NDArray
import cocoindex as coco
from cocoindex.connectors import localfs, postgres
from cocoindex.ops.text import RecursiveSplitter
from cocoindex.ops.sentence_transformers import SentenceTransformerEmbedder
from cocoindex.resources.chunk import Chunk
from cocoindex.resources.file import FileLike, PatternFilePathMatcher
from cocoindex.resources.id import IdGenerator
DATABASE_URL = "postgres://cocoindex:cocoindex@localhost/cocoindex"
PG_DB = coco.ContextKey[asyncpg.Pool]("pg_db")
EMBEDDER = coco.ContextKey[SentenceTransformerEmbedder]("embedder")
_splitter = RecursiveSplitter()
@dataclass
class DocEmbedding:
id: int
filename: str
text: str
embedding: Annotated[NDArray, EMBEDDER] # Dimensions inferred from ContextKey
chunk_start: int
chunk_end: int
@coco.lifespan
async def coco_lifespan(builder: coco.EnvironmentBuilder) -> AsyncIterator[None]:
async with await asyncpg.create_pool(DATABASE_URL) as pool:
builder.provide(PG_DB, pool)
builder.provide(EMBEDDER, SentenceTransformerEmbedder("all-MiniLM-L6-v2"))
yield
@coco.fn
async def process_chunk(
chunk: Chunk, filename: pathlib.PurePath,
id_gen: IdGenerator, table: postgres.TableTarget[DocEmbedding],
) -> None:
table.declare_row(row=DocEmbedding(
id=await id_gen.next_id(chunk.text),
filename=str(filename),
text=chunk.text,
embedding=await coco.use_context(EMBEDDER).embed(chunk.text),
chunk_start=chunk.start.char_offset,
chunk_end=chunk.end.char_offset,
))
@coco.fn(memo=True)
async def process_file(file: FileLike, table: postgres.TableTarget[DocEmbedding]) -> None:
text = await file.read_text()
chunks = _splitter.split(text, chunk_size=2000, chunk_overlap=500)
id_gen = IdGenerator()
await coco.map(process_chunk, chunks, file.file_path.path, id_gen, table)
@coco.fn
async def app_main(sourcedir: pathlib.Path) -> None:
target_table = await postgres.mount_table_target(
PG_DB,
table_name="embeddings",
table_schema=await postgres.TableSchema.from_class(DocEmbedding, primary_key=["id"]),
)
target_table.declare_vector_index(column="embedding")
files = localfs.walk_dir(sourcedir, recursive=True,
path_matcher=PatternFilePathMatcher(included_patterns=["**/*.md"]))
await coco.mount_each(process_file, files.items(), target_table)
app = coco.App(coco.AppConfig(name="Embedding"), app_main, sourcedir=pathlib.Path("./data"))import instructor
from pydantic import BaseModel
from litellm import acompletion
_instructor_client = instructor.from_litellm(acompletion, mode=instructor.Mode.JSON)
class ExtractionResult(BaseModel):
title: str
topics: list[str]
@coco.fn(memo=True) # Memo avoids re-calling LLM
async def extract_and_store(content: str, message_id: int, table) -> None:
result = await _instructor_client.chat.completions.create(
model="gpt-4",
response_model=ExtractionResult,
messages=[{"role": "user", "content": f"Extract topics: {content}"}],
)
table.declare_row(row=Message(id=message_id, title=result.title, content=content))| Connector | Source | Target | Vectors | Use Case |
|---|---|---|---|---|
| PostgreSQL | Y | Y | pgvector | Production SQL + vectors |
| SQLite | - | Y | sqlite-vec | Local SQL + vectors |
| LanceDB | - | Y | Y | Cloud-native vector DB |
| Qdrant | - | Y | Y | Specialized vector DB |
| SurrealDB | - | Y | Y | Graph + document DB |
| Apache Doris | - | Y | Y | Analytical DB + vectors |
| LocalFS | Y | Y | N/A | File-based pipelines |
| Amazon S3 | Y | - | N/A | Cloud object storage |
| Kafka | Y | Y | N/A | Streaming pipelines |
| Google Drive | Y | - | N/A | Cloud file source |
from cocoindex.ops.text import RecursiveSplitter, detect_code_language
splitter = RecursiveSplitter()
language = detect_code_language(filename="example.py")
chunks = splitter.split(text, chunk_size=1000, chunk_overlap=200, language=language)from cocoindex.ops.sentence_transformers import SentenceTransformerEmbedder
embedder = SentenceTransformerEmbedder("sentence-transformers/all-MiniLM-L6-v2")
embedding = await embedder.embed(text) # Returns NDArraycocoindex init my-project # Create new project
cocoindex update main.py # Run app
cocoindex update main.py:my_app # Run specific app
cocoindex update main.py -L # Run in live mode (continuous)
cocoindex update main.py --full-reprocess # Reprocess everything
cocoindex drop main.py [-f] # Drop and reset all state
cocoindex ls [main.py] # List apps
cocoindex show main.py [--tree] # Show component paths@coco.fn@coco.fn@coco.fn(memo=True) # Skip re-execution when inputs/code unchanged
async def process_chunk(chunk, table):
embedding = await embedder.embed(chunk.text) # Expensive!
table.declare_row(...)# Good: Stable identifiers
coco.component_subpath("file", str(file.file_path.path))
coco.component_subpath("record", record.id)
# Bad: Unstable identifiers
coco.component_subpath("file", file) # Object reference
coco.component_subpath("idx", idx) # Index changes@coco.lifespan
async def coco_lifespan(builder: coco.EnvironmentBuilder) -> AsyncIterator[None]:
async with await asyncpg.create_pool(DATABASE_URL) as pool:
builder.provide(PG_DB, pool)
yieldAnnotated[NDArray, CONTEXT_KEY]EMBEDDER = coco.ContextKey[SentenceTransformerEmbedder]("embedder")
@dataclass
class Record:
vector: Annotated[NDArray, EMBEDDER] # Auto-infer dimensions from ContextKey# Mount table target -- subpath is automatic
table = await postgres.mount_table_target(
PG_DB,
table_name="my_table",
table_schema=await postgres.TableSchema.from_class(MyRecord, primary_key=["id"]),
)memo=True@coco.fn(memo=True) # Add this
async def process_item(item):
...examples/README.md.env.exampleexamples/AGENTS.mdtext_embeddingtext_embedding_qdrant_lancedbcode_embeddinghn_trending_topicspatient_intake_extraction_bamlconversation_to_knowledgemeeting_notes_graph_neo4jfiles_transformpdf_to_markdown>=1.0.0| v0 (removed) | v1 equivalent |
|---|---|
| |
| declare target states via Target APIs ( |
| connector APIs, e.g. |
| |
| connector targets, e.g. |
| plain |
| |
CLI | no setup step — just |
references/api_reference.md