1
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Contributing
2
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
3
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Development setup
4
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
5
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```bash
6
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
git clone https://github.com/ConflictHQ/PlanOpticon.git
7
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
cd PlanOpticon
8
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
python -m venv .venv
9
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
source .venv/bin/activate
10
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pip install -e ".[dev]"
11
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
12
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
13
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Running tests
14
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
15
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
PlanOpticon has 822+ tests covering providers, pipeline stages, document processors, knowledge graph operations, exporters, skills, and CLI commands.
16
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
17
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```bash
18
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Run all tests
19
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pytest tests/ -v
20
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
21
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Run with coverage
22
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pytest tests/ --cov=video_processor --cov-report=html
23
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
24
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Run a specific test file
25
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pytest tests/test_models.py -v
26
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
27
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Run tests matching a keyword
28
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pytest tests/ -k "test_knowledge_graph" -v
29
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
30
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Run only fast tests (skip slow integration tests)
31
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pytest tests/ -m "not slow" -v
32
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
33
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
34
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Test conventions
35
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
36
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- All tests live in the `tests/` directory, mirroring the `video_processor/` package structure
37
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Test files are named `test_<module>.py`
38
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Use `pytest` as the test runner -- do not use `unittest.TestCase` unless necessary for specific setup/teardown patterns
39
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Mock external API calls. Never make real API calls in tests. Use `unittest.mock.patch` or `pytest-mock` fixtures to mock provider responses.
40
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Use `tmp_path` (pytest fixture) for any tests that write files to disk
41
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Fixtures shared across test files go in `conftest.py`
42
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- For testing CLI commands, use `click.testing.CliRunner`
43
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- For testing provider implementations, mock at the HTTP client level (e.g., patch `requests.post` or the provider's SDK client)
44
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
45
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Mocking patterns
46
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
47
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
48
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Mocking a provider's chat method
49
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from unittest.mock import MagicMock, patch
50
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
51
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def test_key_point_extraction():
52
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pm = MagicMock()
53
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pm.chat.return_value = '["Point 1", "Point 2"]'
54
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
result = extract_key_points(pm, "transcript text")
55
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
assert len(result) == 2
56
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
57
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Mocking an external API at the HTTP level
58
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
@patch("requests.post")
59
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def test_provider_chat(mock_post):
60
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
mock_post.return_value.json.return_value = {
61
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"choices": [{"message": {"content": "response"}}]
62
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
}
63
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
provider = OpenAIProvider(api_key="test")
64
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
result = provider.chat([{"role": "user", "content": "hello"}])
65
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
assert result == "response"
66
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
67
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
68
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Code style
69
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
70
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
We use:
71
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
72
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **Ruff** for both linting and formatting (100 char line length)
73
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **mypy** for type checking
74
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
75
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Ruff handles all linting (error, warning, pyflakes, and import sorting rules) and formatting in a single tool. There is no need to run Black or isort separately.
76
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
77
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```bash
78
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Lint
79
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ruff check video_processor/
80
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
81
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Format
82
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ruff format video_processor/
83
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
84
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Auto-fix lint issues
85
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ruff check video_processor/ --fix
86
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
87
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Type check
88
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
mypy video_processor/ --ignore-missing-imports
89
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
90
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
91
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Ruff configuration
92
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
93
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
The project's `pyproject.toml` configures ruff as follows:
94
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
95
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```toml
96
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
[tool.ruff]
97
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
line-length = 100
98
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
target-version = "py310"
99
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
100
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
[tool.ruff.lint]
101
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
select = ["E", "F", "W", "I"]
102
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
103
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
104
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
The `I` rule set covers import sorting (equivalent to isort), so imports are automatically organized by ruff.
105
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
106
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Project structure
107
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
108
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
109
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
PlanOpticon/
110
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
├── video_processor/
111
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── cli/ # Click CLI commands
112
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ └── commands.py
113
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── providers/ # LLM/API provider implementations
114
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── base.py # BaseProvider, ProviderRegistry
115
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── manager.py # ProviderManager
116
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── discovery.py # Auto-discovery of available providers
117
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── openai_provider.py
118
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── anthropic_provider.py
119
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── gemini_provider.py
120
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ └── ... # 15+ provider implementations
121
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── sources/ # Cloud and web source connectors
122
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── base.py # BaseSource, SourceFile
123
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── google_drive.py
124
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── zoom_source.py
125
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ └── ... # 20+ source implementations
126
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── processors/ # Document processors
127
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── base.py # DocumentProcessor, registry
128
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── ingest.py # File/directory ingestion
129
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── markdown_processor.py
130
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── pdf_processor.py
131
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ └── __init__.py # Auto-registration of built-in processors
132
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── integrators/ # Knowledge graph and analysis
133
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── knowledge_graph.py # KnowledgeGraph class
134
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── graph_store.py # SQLite graph storage
135
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── graph_query.py # GraphQueryEngine
136
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── graph_discovery.py # Auto-find knowledge_graph.db
137
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ └── taxonomy.py # Planning taxonomy classifier
138
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── agent/ # Planning agent
139
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── orchestrator.py # Agent orchestration
140
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ └── skills/ # Skill implementations
141
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── base.py # Skill ABC, registry, Artifact
142
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── project_plan.py
143
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── prd.py
144
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── roadmap.py
145
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── task_breakdown.py
146
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── doc_generator.py
147
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── wiki_generator.py
148
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── notes_export.py
149
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── artifact_export.py
150
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── github_integration.py
151
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── requirements_chat.py
152
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── cli_adapter.py
153
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ └── __init__.py # Auto-registration of skills
154
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── exporters/ # Output format exporters
155
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── __init__.py
156
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ └── markdown.py # Template-based markdown generation
157
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── utils/ # Shared utilities
158
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── export.py # Multi-format export orchestration
159
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── rendering.py # Mermaid/chart rendering
160
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── prompt_templates.py
161
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ ├── callbacks.py # Progress callback helpers
162
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ │ └── ...
163
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── exchange.py # PlanOpticonExchange format
164
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── pipeline.py # Main video processing pipeline
165
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── models.py # Pydantic data models
166
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ └── output_structure.py # Output directory helpers
167
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
├── tests/ # 822+ tests
168
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
├── knowledge-base/ # Local-first graph tools
169
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ ├── viewer.html # Self-contained D3.js graph viewer
170
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ └── query.py # Python query script (NetworkX)
171
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
├── docs/ # MkDocs documentation
172
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
└── pyproject.toml # Project configuration
173
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
174
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
175
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
See [Architecture Overview](architecture/overview.md) for a more detailed breakdown of module responsibilities.
176
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
177
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Adding a new provider
178
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
179
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Providers self-register via `ProviderRegistry.register()` at module level. When the provider module is imported, it registers itself automatically.
180
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
181
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
1. Create `video_processor/providers/your_provider.py`
182
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
2. Extend `BaseProvider` from `video_processor/providers/base.py`
183
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
3. Implement the four required methods: `chat()`, `analyze_image()`, `transcribe_audio()`, `list_models()`
184
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
4. Call `ProviderRegistry.register()` at module level
185
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
5. Add the import to `video_processor/providers/manager.py` in the lazy-import block
186
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
6. Add tests in `tests/test_providers.py`
187
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
188
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Example provider skeleton
189
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
190
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
191
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Your provider implementation."""
192
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
193
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from video_processor.providers.base import BaseProvider, ModelInfo, ProviderRegistry
194
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
195
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
196
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
class YourProvider(BaseProvider):
197
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
provider_name = "yourprovider"
198
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
199
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def __init__(self, api_key: str | None = None):
200
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
import os
201
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
self.api_key = api_key or os.environ.get("YOUR_API_KEY", "")
202
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
203
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def chat(self, messages, max_tokens=4096, temperature=0.7, model=None):
204
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Implement chat completion
205
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
...
206
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
207
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def analyze_image(self, image_bytes, prompt, max_tokens=4096, model=None):
208
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Implement image analysis
209
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
...
210
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
211
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def transcribe_audio(self, audio_path, language=None, model=None):
212
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Implement audio transcription (or raise NotImplementedError)
213
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
...
214
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
215
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def list_models(self):
216
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return [ModelInfo(id="your-model", provider="yourprovider", capabilities=["chat"])]
217
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
218
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
219
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Self-registration at import time
220
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ProviderRegistry.register(
221
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"yourprovider",
222
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
YourProvider,
223
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
env_var="YOUR_API_KEY",
224
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
model_prefixes=["your-"],
225
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
default_models={"chat": "your-model"},
226
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
227
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
228
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
229
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### OpenAI-compatible providers
230
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
231
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
For providers that use the OpenAI API format, extend `OpenAICompatibleProvider` instead of `BaseProvider`. This provides default implementations of `chat()`, `analyze_image()`, and `list_models()` -- you only need to configure the base URL and model mappings.
232
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
233
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
234
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from video_processor.providers.base import OpenAICompatibleProvider, ProviderRegistry
235
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
236
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
class YourProvider(OpenAICompatibleProvider):
237
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
provider_name = "yourprovider"
238
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
base_url = "https://api.yourprovider.com/v1"
239
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
env_var = "YOUR_API_KEY"
240
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
241
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ProviderRegistry.register("yourprovider", YourProvider, env_var="YOUR_API_KEY")
242
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
243
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
244
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Adding a new cloud source
245
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
246
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Source connectors implement the `BaseSource` ABC from `video_processor/sources/base.py`. Authentication is handled per-source, typically via environment variables.
247
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
248
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
1. Create `video_processor/sources/your_source.py`
249
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
2. Extend `BaseSource`
250
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
3. Implement `authenticate()`, `list_videos()`, and `download()`
251
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
4. Add the class to the lazy-import map in `video_processor/sources/__init__.py`
252
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
5. Add CLI commands in `video_processor/cli/commands.py` if needed
253
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
6. Add tests and documentation
254
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
255
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Example source skeleton
256
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
257
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
258
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Your source integration."""
259
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
260
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
import os
261
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
import logging
262
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from pathlib import Path
263
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from typing import List, Optional
264
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
265
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from video_processor.sources.base import BaseSource, SourceFile
266
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
267
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
logger = logging.getLogger(__name__)
268
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
269
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
270
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
class YourSource(BaseSource):
271
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def __init__(self, api_key: Optional[str] = None):
272
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
self.api_key = api_key or os.environ.get("YOUR_SOURCE_KEY", "")
273
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
274
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def authenticate(self) -> bool:
275
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Validate credentials. Return True on success."""
276
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not self.api_key:
277
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
logger.error("API key not set. Set YOUR_SOURCE_KEY env var.")
278
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return False
279
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Make a test API call to verify credentials
280
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
...
281
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return True
282
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
283
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def list_videos(
284
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
self,
285
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
folder_id: Optional[str] = None,
286
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
folder_path: Optional[str] = None,
287
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
patterns: Optional[List[str]] = None,
288
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
) -> List[SourceFile]:
289
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""List available video files."""
290
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
...
291
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
292
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def download(self, file: SourceFile, destination: Path) -> Path:
293
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Download a single file. Return the local path."""
294
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
destination.parent.mkdir(parents=True, exist_ok=True)
295
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Download file content to destination
296
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
...
297
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return destination
298
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
299
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
300
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Registering in `__init__.py`
301
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
302
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Add your source to the `__all__` list and the `_lazy_map` dictionary in `video_processor/sources/__init__.py`:
303
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
304
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
305
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
__all__ = [
306
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
...
307
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"YourSource",
308
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
]
309
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
310
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
_lazy_map = {
311
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
...
312
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"YourSource": "video_processor.sources.your_source",
313
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
}
314
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
315
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
316
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Adding a new skill
317
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
318
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Agent skills extend the `Skill` ABC from `video_processor/agent/skills/base.py` and self-register via `register_skill()`.
319
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
320
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
1. Create `video_processor/agent/skills/your_skill.py`
321
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
2. Extend `Skill` and set `name` and `description` class attributes
322
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
3. Implement `execute()` to return an `Artifact`
323
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
4. Optionally override `can_execute()` for custom precondition checks
324
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
5. Call `register_skill()` at module level
325
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
6. Add the import to `video_processor/agent/skills/__init__.py`
326
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
7. Add tests
327
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
328
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Example skill skeleton
329
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
330
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
331
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Your custom skill."""
332
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
333
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from video_processor.agent.skills.base import AgentContext, Artifact, Skill, register_skill
334
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
335
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
336
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
class YourSkill(Skill):
337
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
name = "your_skill"
338
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
description = "Generates a custom artifact from the knowledge graph."
339
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
340
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def execute(self, context: AgentContext, **kwargs) -> Artifact:
341
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Generate the artifact."""
342
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
kg_data = context.knowledge_graph.to_dict()
343
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Build content from knowledge graph data
344
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content = f"# Your Artifact\n\n{len(kg_data.get('entities', []))} entities found."
345
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return Artifact(
346
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
name="your_artifact",
347
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content=content,
348
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
artifact_type="document",
349
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
format="markdown",
350
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
351
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
352
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def can_execute(self, context: AgentContext) -> bool:
353
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Check prerequisites (default requires KG + provider)."""
354
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return context.knowledge_graph is not None
355
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
356
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
357
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Self-registration at import time
358
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
register_skill(YourSkill())
359
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
360
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
361
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Registering in `__init__.py`
362
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
363
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Add the import to `video_processor/agent/skills/__init__.py` so the skill is loaded (and self-registered) when the skills package is imported:
364
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
365
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
366
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from video_processor.agent.skills import (
367
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
...
368
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
your_skill, # noqa: F401
369
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
370
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
371
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
372
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Adding a new document processor
373
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
374
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Document processors extend the `DocumentProcessor` ABC from `video_processor/processors/base.py` and are registered via `register_processor()`.
375
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
376
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
1. Create `video_processor/processors/your_processor.py`
377
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
2. Extend `DocumentProcessor`
378
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
3. Set `supported_extensions` class attribute
379
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
4. Implement `process()` (returns `List[DocumentChunk]`) and `can_process()`
380
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
5. Call `register_processor()` at module level
381
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
6. Add the import to `video_processor/processors/__init__.py`
382
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
7. Add tests
383
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
384
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Example processor skeleton
385
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
386
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
387
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Your document processor."""
388
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
389
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from pathlib import Path
390
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from typing import List
391
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
392
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from video_processor.processors.base import (
393
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
DocumentChunk,
394
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
DocumentProcessor,
395
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
register_processor,
396
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
397
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
398
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
399
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
class YourProcessor(DocumentProcessor):
400
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
supported_extensions = [".xyz", ".abc"]
401
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
402
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def can_process(self, path: Path) -> bool:
403
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return path.suffix.lower() in self.supported_extensions
404
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
405
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def process(self, path: Path) -> List[DocumentChunk]:
406
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
text = path.read_text()
407
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Split into chunks as appropriate for your format
408
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return [
409
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
DocumentChunk(
410
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
text=text,
411
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
source_file=str(path),
412
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
chunk_index=0,
413
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
metadata={"format": "xyz"},
414
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
415
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
]
416
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
417
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
418
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Self-registration at import time
419
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
register_processor([".xyz", ".abc"], YourProcessor)
420
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
421
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
422
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Registering in `__init__.py`
423
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
424
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Add the import to `video_processor/processors/__init__.py`:
425
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
426
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
427
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from video_processor.processors import (
428
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
markdown_processor, # noqa: F401, E402
429
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pdf_processor, # noqa: F401, E402
430
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
your_processor, # noqa: F401, E402
431
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
432
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
433
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
434
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Adding a new exporter
435
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
436
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Exporters live in `video_processor/exporters/` and are typically called from CLI commands. There is no strict ABC for exporters -- they are plain functions that accept knowledge graph data and an output directory.
437
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
438
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
1. Create `video_processor/exporters/your_exporter.py`
439
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
2. Implement one or more export functions that accept KG data (as a dict) and an output path
440
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
3. Add CLI integration in `video_processor/cli/commands.py` under the `export` group
441
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
4. Add tests
442
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
443
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Example exporter skeleton
444
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
445
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
446
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Your exporter."""
447
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
448
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
import json
449
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from pathlib import Path
450
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from typing import List
451
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
452
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
453
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def export_your_format(kg_data: dict, output_dir: Path) -> List[Path]:
454
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Export knowledge graph data in your format.
455
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
456
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Args:
457
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
kg_data: Knowledge graph as a dict (from KnowledgeGraph.to_dict()).
458
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
output_dir: Directory to write output files.
459
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
460
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Returns:
461
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
List of created file paths.
462
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""
463
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
output_dir.mkdir(parents=True, exist_ok=True)
464
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
created = []
465
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
466
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
output_file = output_dir / "export.xyz"
467
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
output_file.write_text(json.dumps(kg_data, indent=2))
468
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
created.append(output_file)
469
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
470
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return created
471
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
472
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
473
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Adding the CLI command
474
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
475
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Add a subcommand under the `export` group in `video_processor/cli/commands.py`:
476
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
477
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
478
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
@export.command("your-format")
479
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
@click.argument("db_path", type=click.Path(exists=True))
480
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
@click.option("-o", "--output", type=click.Path(), default=None)
481
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def export_your_format_cmd(db_path, output):
482
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Export knowledge graph in your format."""
483
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from video_processor.exporters.your_exporter import export_your_format
484
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from video_processor.integrators.knowledge_graph import KnowledgeGraph
485
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
486
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
kg = KnowledgeGraph(db_path=Path(db_path))
487
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
out_dir = Path(output) if output else Path.cwd() / "your-export"
488
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
created = export_your_format(kg.to_dict(), out_dir)
489
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
click.echo(f"Exported {len(created)} files to {out_dir}/")
490
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
491
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
492
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## License
493
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
494
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
MIT License -- Copyright (c) 2026 CONFLICT LLC. All rights reserved.
495
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!