You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Added Tags system: Module tags (capability, platform, vendor) + FrameType tags (media, encoding, format). Tags on FrameTypes, NOT on PinDefs.
3.0
Dec 2024
Initial RFC with C++ as single source of truth, Static/Dynamic properties, Controller modules
Elevator Pitch
Today: Building an ApraPipes pipeline requires writing C++ code—instantiating modules, setting properties, connecting pins, handling errors. This is powerful but creates friction: every new pipeline needs a developer, every change needs a recompile, and onboarding new users means teaching them C++.
Tomorrow: Describe your pipeline in a simple text file:
[modules.camera]
type = "RTSPSourceModule"
[modules.camera.props]
url = "rtsp://camera.local/stream"
[modules.motion]
type = "MotionDetectorModule"
[modules.motion.props]
sensitivity = 0.7
[[connections]]
from = "camera.output"to = "motion.input"
Or better yet, just tell an LLM what you want:
"Monitor my RTSP camera for motion, save 10-second clips when detected, and let me adjust sensitivity via REST API"
...and get a validated, runnable pipeline.
The Vision:
Non-developers can create and modify pipelines
LLMs can generate pipelines from natural language
Validation catches errors before runtime
Runtime control via Controller modules (REST API, schedulers, UI)
Zero drift between code and documentation—C++ is the single source of truth
Executive Summary
This RFC proposes a declarative pipeline definition system for ApraPipes with:
C++ as Single Source of Truth — All metadata (modules, pins, properties, frame types) defined in C++ headers
Frame types carry tags that describe their media characteristics. This enables LLM queries like "find modules that output video" by checking output pin frame types for the video tag.
// ============================================================// Frame types with tags// ============================================================classH264Frame : publicEncodedVideoFrame {
structMetadata {
staticconstexpr std::string_view name = "H264Frame";
staticconstexpr std::string_view parent = "EncodedVideoFrame";
staticconstexpr std::string_view description =
"H.264/AVC encoded video frame (NAL units)";
// Tags describe what this frame type representsstaticconstexpr std::array tags = {
"video", // Media type"encoded", // Encoding state"h264"// Specific codec
};
staticconstexpr std::array attributes = {
AttrDef::Int("width", true, "Frame width in pixels"),
AttrDef::Int("height", true, "Frame height in pixels"),
AttrDef::Bool("is_keyframe", true, "Whether this is an IDR frame")
};
};
};
classRawImagePlanar : publicVideoFrame {
structMetadata {
staticconstexpr std::string_view name = "RawImagePlanar";
staticconstexpr std::string_view parent = "VideoFrame";
staticconstexpr std::string_view description =
"Raw planar image (NV12, I420, YUV444)";
staticconstexpr std::array tags = {
"video", // Media type"raw", // Encoding state"planar", // Memory layout"yuv"// Color space family
};
staticconstexpr std::array attributes = {
AttrDef::Int("width", true),
AttrDef::Int("height", true),
AttrDef::Enum("format", {"NV12", "I420", "YUV444"}, true)
};
};
};
classDetectionResultFrame : publicMetadataFrame {
structMetadata {
staticconstexpr std::string_view name = "DetectionResultFrame";
staticconstexpr std::string_view parent = "MetadataFrame";
staticconstexpr std::string_view description =
"Object detection results with bounding boxes and labels";
staticconstexpr std::array tags = {
"metadata", // Media type (not video/audio)"detection", // Semantic meaning"bounding_boxes"// Data structure
};
};
};
classAudioPCMFrame : publicAudioFrame {
structMetadata {
staticconstexpr std::string_view name = "AudioPCMFrame";
staticconstexpr std::string_view parent = "AudioFrame";
staticconstexpr std::array tags = {
"audio", // Media type"raw", // Encoding state"pcm"// Format
};
};
};
LLM Query Flow with Tags
User: "I need a module that decodes video"
LLM reasoning:
1. Find modules with tag "decoder" → H264DecoderNvCodec, JPEGDecoder, ...
2. Check their output pins
3. Look up FrameType for each output pin
4. Filter where FrameType has tag "video" → H264DecoderNvCodec ✓
Result: H264DecoderNvCodec
[pipeline]
name = "motion_recording_with_api"description = "Motion detection pipeline with REST API control"# Data modules
[modules.rtsp_source]
type = "RTSPSourceModule"
[modules.rtsp_source.props]
url = "rtsp://camera.local:554/stream"
[modules.decoder]
type = "H264DecoderNvCodec"
[modules.decoder.props]
device_id = 0
[modules.motion]
type = "MotionDetectorModule"
[modules.motion.props]
sensitivity = 0.5# Can be changed via APImin_area_percent = 2.0# Can be changed via APIenabled = true# Can be toggled via API
[modules.recorder]
type = "FileWriterModule"
[modules.recorder.props]
path = "/recordings"# Controller module - controls the pipeline via REST API
[modules.api]
type = "RestApiController"
[modules.api.props]
port = 8080bind_address = "0.0.0.0"# Data connections
[[connections]]
from = "rtsp_source.output"to = "decoder.input"
[[connections]]
from = "decoder.output"to = "motion.input"
[[connections]]
from = "motion.motion_frames"to = "recorder.input"# Note: Controller modules don't need data connections# They connect to the pipeline control plane automatically
API Generated from Pipeline
The REST API controller would automatically expose:
GET /api/modules # List all modules
GET /api/modules/motion/properties # Get motion detector properties
PUT /api/modules/motion/properties/sensitivity
Body: { "value": 0.8 } # Set sensitivity (dynamic)
GET /api/modules/motion/properties/frame_buffer_size
Response: { "value": 5, "mutability": "static", "error": null }
PUT /api/modules/motion/properties/frame_buffer_size
Response: { "error": "Property 'frame_buffer_size' is static and cannot be modified at runtime" }
5. Pipeline Validator
Validation Phases
// ============================================================// File: core/PipelineValidator.h// ============================================================
#pragma once
#include"PipelineDescription.h"
#include"ModuleRegistry.h"
#include"FrameTypeRegistry.h"
#include<vector>
#include<string>namespaceapra {
structValidationIssue {
enumclassLevel {
Error, // Pipeline cannot be built
Warning, // Pipeline can be built but may have issues
Info // Informational (e.g., defaults applied)
};
Level level;
std::string code; // Machine-readable: "UNKNOWN_MODULE", "TYPE_MISMATCH"
std::string location; // "modules.decoder.props.device_id"
std::string message; // Human-readable description// For schema evolution warnings
std::string suggestion = ""; // "Add property 'foo' with value 'bar'"
};
classPipelineValidator {
public:structResult {
std::vector<ValidationIssue> issues;
boolhasErrors() const;
boolhasWarnings() const;
std::vector<ValidationIssue> errors() const;
std::vector<ValidationIssue> warnings() const;
};
// Full validation (all phases)
Result validate(const PipelineDescription& desc) const;
// Individual phases (for tooling)
Result validateModules(const PipelineDescription& desc) const;
Result validateProperties(const PipelineDescription& desc) const;
Result validateConnections(const PipelineDescription& desc) const;
Result validateGraph(const PipelineDescription& desc) const;
Result validateSchemaVersion(const PipelineDescription& desc) const;
private:// Property validation helpersboolvalidateIntRange(int64_t value, const PropDef& prop) const;
boolvalidateFloatRange(double value, const PropDef& prop) const;
boolvalidateRegex(const std::string& value, const PropDef& prop) const;
boolvalidateEnum(const std::string& value, const PropDef& prop) const;
};
} // namespace apra
Validation Rules
Phase 1: Module Validation
For each module instance:
✓ Module type exists in registry
✓ Module type matches category constraints (if any)
! Warn if module version differs from registry version
Phase 2: Property Validation
For each property in each module:
✓ Property name exists in module's Metadata
✓ Property type matches (int, float, bool, string, enum)
✓ Value is within declared range (min/max)
✓ String matches regex pattern (if declared)
✓ Enum value is in allowed set
! Warn if required property missing (will use default)
! Info: "Property 'X' not specified, using default 'Y'"
Phase 3: Connection Validation
For each connection:
✓ Source module exists
✓ Source pin exists on source module
✓ Destination module exists
✓ Destination pin exists on destination module
✓ Frame types are compatible:
- Direct match, OR
- Source type is subtype of input type, OR
- Conversion path exists (if auto-convert enabled)
✓ No duplicate connections to same input pin
✓ Required input pins are connected
Phase 4: Graph Validation
✓ Pipeline has at least one source module (Category::Source)
✓ Graph is a DAG (no cycles)
! Warn if any module has unconnected optional outputs
! Warn if any non-sink module has no outgoing connections
Phase 5: Schema Version Validation
For each module instance:
If module's schema version > version in pipeline file:
! Warn about new required properties
! Suggest adding properties with defaults
If module's schema version < version in pipeline file:
✓ OK (forward compatibility)
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
RFC: Declarative Pipeline Construction for ApraPipes
Version: 4.0
Status: Draft
Author: Akhil / Apra Labs
Changelog
Elevator Pitch
Today: Building an ApraPipes pipeline requires writing C++ code—instantiating modules, setting properties, connecting pins, handling errors. This is powerful but creates friction: every new pipeline needs a developer, every change needs a recompile, and onboarding new users means teaching them C++.
Tomorrow: Describe your pipeline in a simple text file:
Or better yet, just tell an LLM what you want:
...and get a validated, runnable pipeline.
The Vision:
Executive Summary
This RFC proposes a declarative pipeline definition system for ApraPipes with:
Table of Contents
1. Architecture Overview
Design Principles
System Diagram
2. Core C++ Infrastructure
Intermediate Representation
All frontends parse into this common C++ structure:
Metadata Types
3. Property System (Static vs Dynamic)
Rationale
ApraPipes has an existing dynamic properties framework. Properties fall into two categories:
device_id,url,codecsensitivity,bitrate,enabledThe Metadata must declare which is which to:
Module Example with Mixed Properties
Generated Schema (Build Artifact)
The build system generates this from the Metadata:
{ "modules": { "MotionDetectorModule": { "category": "analytics", "tags": ["detector", "motion", "opencv", "cpu_only"], "description": "Detects motion in video frames...", "version": "1.0", "properties": { "frame_buffer_size": { "type": "int", "mutability": "static", "default": 5, "min": 2, "max": 30, "description": "Number of frames for background model" }, "sensitivity": { "type": "float", "mutability": "dynamic", "default": 0.5, "min": 0.0, "max": 1.0, "description": "Motion sensitivity (0=low, 1=high)" } } } } }FrameType Tags
Frame types carry tags that describe their media characteristics. This enables LLM queries like "find modules that output video" by checking output pin frame types for the
videotag.LLM Query Flow with Tags
Tag Taxonomy Reference
encoder,decoder,detector,tracker,filter,reader,writer,muxer,demuxercuda_required,cuda_optional,jetson_only,cpu_only,arm64_only,x86_64_onlynvidia,intel,v4l2,opencv,ffmpegh264,h265,jpeg,mp4,rtsp,webrtcvideo,audio,image,textraw,encoded,compressedh264,h265,jpeg,pcm,nv12,rgb,yuv,bgrplanar,packed,interleavedmetadata,detection,motion,transcript,commandExample: REST API Controller
Example: Timer-Based Controller
Pipeline Definition with Controller
API Generated from Pipeline
The REST API controller would automatically expose:
5. Pipeline Validator
Validation Phases
Validation Rules
Phase 1: Module Validation
Phase 2: Property Validation
Phase 3: Connection Validation
Phase 4: Graph Validation
Phase 5: Schema Version Validation
Error Codes
6. Module Factory
Factory Implementation
Usage Pattern
7. Frontend Parsers
Parser Interface
All parsers implement this interface and produce the same
PipelineDescription:TOML Parser (Primary)
Adding a New Parser
Adding YAML support is just implementing the interface:
8. Build System & Versioning
Schema Generation
Automated Version Detection
CI Integration
9. LLM Integration
Context Generation
)";
}
Usage
10. Implementation Phases
Phase 1: Core Infrastructure (MVP) — 6 weeks
Goal: Validate end-to-end flow with minimal feature set
Metadata.hwith PinDef, PropDef (including Static/Dynamic)ModuleRegistrywith registration and queriesFrameTypeRegistrywith hierarchyREGISTER_MODULEmacroRTSPSourceModule(Source)H264DecoderNvCodec(Transform)MotionDetectorModule(Analytics)FileWriterModule(Sink)RestApiController(Controller)PipelineDescriptionintermediate representationTomlParser(using toml++ or toml11)PipelineValidator(Phase 1-3 validation)ModuleFactory(basic construction)aprapipes validate pipeline.tomlaprapipes run pipeline.tomlPhase 2: Complete Validation — 3 weeks
CompatibilityRegistryfor conversionsaprapipes list-modulesaprapipes describe <module>Phase 3: All Modules — 4 weeks
Phase 4: LLM Integration — 2 weeks
LLMPipelineGeneratorwith iterative refinementaprapipes generate "<description>"Phase 5: Tooling & Polish — 2 weeks
Appendix A: Complete Module Example
Appendix B: Pipeline TOML Reference
References
Beta Was this translation helpful? Give feedback.
All reactions