This repository contains SDKs and CLI for invoking Metadata Dynamic Agents.
metadata-ai-sdk/
├── cli/ # Rust CLI (ai-sdk)
├── python/ # Python SDK (ai-sdk on PyPI)
│ ├── src/ai_sdk/ # Source code
│ ├── tests/ # Unit and integration tests
│ └── pyproject.toml # Package config
├── typescript/ # TypeScript SDK (@openmetadata/ai-sdk on npm)
├── java/ # Java SDK (org.open-metadata:ai-sdk)
├── n8n-nodes-metadata/ # n8n community node (depends on TypeScript SDK)
├── cookbook/ # Guides, scripts, and resources for common use cases
│ └── resources/
│ └── demo-database/ # Jaffle Shop demo (PostgreSQL + Metabase + dbt)
│ ├── docker/ # docker-compose.yml, Dockerfile, init.sql
│ └── dbt/ # dbt project (models, profiles, tests)
├── scripts/ # Development scripts (pre-commit hooks)
├── .claude/skills/ # Claude Code skills for this repo
├── VERSION # Single source of truth for version
└── Makefile # Cross-SDK commands (single source of truth)
# Linting & Formatting
make lint # Check all SDKs (CI equivalent)
make format # Fix all SDKs (pre-commit equivalent)
# Testing
make test-all # Run all unit tests
make test-integration # Run integration tests (requires AI_SDK_HOST, AI_SDK_TOKEN)
# Building
make build-all # Build all SDKs
# Version Management
make version # Show current version
make bump-version V=X.Y.Z # Update all SDKs
# Local Development
make install-local # Install Python SDK in editable mode (all extras)
# Demo Database
make install-dbt # Install dbt-postgres for the demo
make demo-database # Start PostgreSQL (port 5433) + Metabase (port 3000)
make demo-database-stop # Stop demo database containers
make demo-dbt # Run dbt models and tests against the demo database
make demo-gdpr # Start the GDPR DSAR compliance demo (port 8080)
# Git Hooks
make install-hooks # Install pre-commit hooks- Multi-SDK Consistency - All SDKs implement the same API surface with language-idiomatic patterns
- Zero/Minimal Dependencies - TypeScript has zero runtime deps; others minimize external deps
- Streaming First - SSE streaming is a primary use case, not an afterthought
- Typed Errors - Structured error hierarchy for proper error handling
client = AISdk(host, token)
# default AskCollate agent
response = client.agent().invoke("message")
# Named dynamic agent
response = client.agent("agent-name").invoke("message")
# Streaming (works with both)
for event in client.agent().stream("message"):
handle(event)
# Entity CRUD lives on namespaces (composition pattern)
client.agents.list()
client.bots.get("ingestion-bot")
client.personas.list()
client.skills.get("DataQuality")
# Context Center memories
client.memories.list(primary_entity_fqn="db.tbl")
client.memories.create(CreateContextMemoryRequest(name="m1", question="...", answer="..."))
results = client.memories.search("explain customer churn")
message- Partial response contenttool_call- Agent calling a tooltool_result- Tool resulterror- Error occurreddone- Stream complete
AISdkError (base)
├── AuthenticationError (401)
├── AuthorizationError (403)
├── NotFoundError (404)
├── ValidationError (400)
├── RateLimitError (429)
└── ServerError (5xx)
| Concept | Python | TypeScript | Java | Rust |
|---|---|---|---|---|
| Methods | snake_case |
camelCase |
camelCase |
snake_case |
| Classes | PascalCase |
PascalCase |
PascalCase |
PascalCase |
| Files | snake_case.py |
camelCase.ts |
PascalCase.java |
snake_case.rs |
When adding functionality, update ALL SDKs:
-
Python (
python/src/ai_sdk/)- Add to
_client.pyor_agent.py - Add types to models
- Add tests in
python/tests/
- Add to
-
TypeScript (
typescript/src/)- Add to
agent.ts - Add interfaces to
models.ts - Add tests in
typescript/tests/
- Add to
-
Java (
java/src/main/java/io/metadata/ai/)- Add to
AgentHandle.java - Add model classes in
models/ - Add tests in
java/src/test/
- Add to
-
Rust CLI (
cli/src/)- Add subcommand in
main.rs - Add tests
- Add subcommand in
-
Verify
make lint && make test-all
Unit Tests - Mock HTTP responses, no real API calls:
cd python && pytest --ignore=tests/integration
cd typescript && npm test
cd java && mvn test -DexcludedGroups=integration
cd cli && cargo test --libIntegration Tests - Real API calls (requires credentials):
export AI_SDK_HOST=https://your-instance.getcollate.io
export AI_SDK_TOKEN=your-jwt-token
export AI_SDK_TEST_AGENT=agent-name # optional
make test-integration| SDK | Formatter | Linter | Type Checker |
|---|---|---|---|
| Python | ruff format |
ruff check |
ty |
| Rust | cargo fmt |
cargo clippy |
(built-in) |
| TypeScript | ESLint | ESLint | tsc |
| Java | Spotless | Spotless | javac |
Install with make install-hooks. Runs on staged files:
- Detects which SDKs have changes
- Runs formatter (auto-fixes)
- Runs linter
- Runs type checker (Python)
- Re-stages fixed files
Every PR runs:
- Linting (format check + lint + types)
- Unit tests with coverage
- Security scanning (CodeQL, dependency audit)
Use these with /skill-name in Claude Code:
/add-sdk-method- Add a method across all SDKs/add-test- Add unit or integration tests/run-integration- Run integration tests/debug-ci- Diagnose CI failures/validate-docs- Validate documentation for clarity and completeness
A local PostgreSQL + Metabase + dbt setup at cookbook/resources/demo-database/ for testing cookbook features (DQ, lineage, PII classification, etc.).
# 1. Start containers (requires Docker)
make demo-database
# 2. Run dbt models (requires dbt-postgres: pip install dbt-postgres)
make demo-dbt| Service | Host | Port | User | Password |
|---|---|---|---|---|
| PostgreSQL | localhost | 5433 | jaffle_user |
jaffle_pass |
| PostgreSQL | localhost | 5433 | openmetadata_user |
openmetadata_pass |
| Metabase | localhost | 3000 | admin@jaffle.shop |
JaffleAdmin123! |
Port 5433 is used intentionally to avoid conflicts with a local PostgreSQL on the default 5432.
docker/docker-compose.yml— PostgreSQL (port 5433) and Metabase servicesdocker/init.sql— Schema creation, seed data, analytics views, user grantsdbt/profiles.yml— dbt connection profile (points to localhost:5433)dbt/models/— staging, intermediate, and marts models
make demo-database-stop # Stop containers
# To also remove data volumes:
cd cookbook/resources/demo-database/docker && docker-compose down -vWhy multiple SDKs in one repo?
- Users work in different languages (Python for data engineers, TypeScript for web, Java for enterprise)
- Single repo ensures version consistency and coordinated releases
- Every feature must be implemented in ALL SDKs - no partial implementations
Why zero runtime dependencies (TypeScript)?
- Minimal bundle size (~10KB)
- Works everywhere: Node.js 18+, browsers, Deno, Bun, edge runtimes
- No dependency vulnerabilities to track
- Native
fetchAPI is sufficient; SSE parsing is simple enough to implement
Why SSE streaming over WebSockets?
- Simpler protocol, one-way communication is sufficient
- Better HTTP/2 support
- Each SDK exposes streaming idiomatically: async generators (Python), async iterators (TypeScript), callbacks/streams (Java)
Why typed error hierarchy?
- Users can catch specific errors (AuthenticationError vs NotFoundError)
- Enables proper error handling (refresh token on 401, retry on 429)
- Consistent across all SDKs
Why single VERSION file?
- One source of truth prevents version drift
make bump-version V=X.Y.Zupdates all SDKs atomically- CI validates all versions match
- Python files: You MUST invoke
/dignified-pythonbefore writing, reviewing, or refactoring ANY Python file (.py). This is non-negotiable — no Python code changes without loading the skill first.
- Edit files in
python/src/ai_sdk/generated/(auto-generated) - Change version numbers manually (use
make bump-version) - Add a method to only one SDK (all must be updated)
- Add dependencies without strong justification
- Skip tests for new functionality
- Use different naming conventions than established patterns
- Commit without running
make lint
| Issue | Solution |
|---|---|
| Pre-commit fails | Run make format then commit |
| Type errors (Python) | Check type annotations, run ty check src |
| Import errors | Check __init__.py exports |
| n8n build fails | Build TypeScript SDK first: cd typescript && npm run build |
| Integration tests skip | Set AI_SDK_HOST and AI_SDK_TOKEN env vars |