caption-flow 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- caption_flow/__init__.py +3 -2
- caption_flow/cli.py +56 -39
- caption_flow/models.py +6 -4
- caption_flow/monitor.py +12 -2
- caption_flow/orchestrator.py +729 -217
- caption_flow/storage.py +579 -222
- caption_flow/utils/__init__.py +3 -1
- caption_flow/utils/auth.py +24 -25
- caption_flow/utils/checkpoint_tracker.py +92 -0
- caption_flow/utils/chunk_tracker.py +278 -194
- caption_flow/utils/dataset_loader.py +392 -73
- caption_flow/utils/image_processor.py +121 -1
- caption_flow/utils/prompt_template.py +137 -0
- caption_flow/utils/shard_processor.py +315 -0
- caption_flow/utils/shard_tracker.py +87 -0
- caption_flow/workers/base.py +228 -0
- caption_flow/workers/caption.py +1321 -0
- caption_flow/{worker_data.py → workers/data.py} +162 -234
- caption_flow-0.2.0.dist-info/METADATA +369 -0
- caption_flow-0.2.0.dist-info/RECORD +29 -0
- caption_flow/worker.py +0 -300
- caption_flow/worker_vllm.py +0 -1028
- caption_flow-0.1.0.dist-info/METADATA +0 -427
- caption_flow-0.1.0.dist-info/RECORD +0 -25
- {caption_flow-0.1.0.dist-info → caption_flow-0.2.0.dist-info}/WHEEL +0 -0
- {caption_flow-0.1.0.dist-info → caption_flow-0.2.0.dist-info}/entry_points.txt +0 -0
- {caption_flow-0.1.0.dist-info → caption_flow-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {caption_flow-0.1.0.dist-info → caption_flow-0.2.0.dist-info}/top_level.txt +0 -0
caption_flow/__init__.py
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
__version__ = "0.1.0"
|
4
4
|
|
5
5
|
from .orchestrator import Orchestrator
|
6
|
-
from .
|
6
|
+
from .workers.data import DataWorker
|
7
|
+
from .workers.caption import CaptionWorker
|
7
8
|
from .monitor import Monitor
|
8
9
|
|
9
|
-
__all__ = ["Orchestrator", "
|
10
|
+
__all__ = ["Orchestrator", "DataWorker", "CaptionWorker", "Monitor"]
|
caption_flow/cli.py
CHANGED
@@ -15,7 +15,6 @@ from rich.logging import RichHandler
|
|
15
15
|
from datetime import datetime
|
16
16
|
|
17
17
|
from .orchestrator import Orchestrator
|
18
|
-
from .worker import Worker
|
19
18
|
from .monitor import Monitor
|
20
19
|
from .utils.certificates import CertificateManager
|
21
20
|
|
@@ -168,8 +167,6 @@ def orchestrator(ctx, config: Optional[str], **kwargs):
|
|
168
167
|
else:
|
169
168
|
config_data = base_config
|
170
169
|
|
171
|
-
console.print(f"Config contents: {config_data}")
|
172
|
-
|
173
170
|
# Apply CLI overrides
|
174
171
|
if kwargs.get("port"):
|
175
172
|
config_data["port"] = kwargs["port"]
|
@@ -242,11 +239,11 @@ def worker(ctx, config: Optional[str], **kwargs):
|
|
242
239
|
|
243
240
|
# Choose worker type
|
244
241
|
if kwargs.get("vllm") or config_data.get("vllm"):
|
245
|
-
from .
|
242
|
+
from .workers.caption import CaptionWorker
|
246
243
|
|
247
|
-
worker_instance =
|
244
|
+
worker_instance = CaptionWorker(config_data)
|
248
245
|
else:
|
249
|
-
|
246
|
+
raise ValueError(f"Not sure how to handle worker for {config_data.get('type')} type setup.")
|
250
247
|
|
251
248
|
try:
|
252
249
|
asyncio.run(worker_instance.start())
|
@@ -262,84 +259,92 @@ def worker(ctx, config: Optional[str], **kwargs):
|
|
262
259
|
@click.option("--no-verify-ssl", is_flag=True, help="Skip SSL verification")
|
263
260
|
@click.option("--debug", is_flag=True, help="Enable debug output")
|
264
261
|
@click.pass_context
|
265
|
-
def monitor(
|
266
|
-
|
262
|
+
def monitor(
|
263
|
+
ctx,
|
264
|
+
config: Optional[str],
|
265
|
+
server: Optional[str],
|
266
|
+
token: Optional[str],
|
267
|
+
no_verify_ssl: bool,
|
268
|
+
debug: bool,
|
269
|
+
):
|
267
270
|
"""Start the monitoring TUI."""
|
268
|
-
|
271
|
+
|
269
272
|
# Enable debug logging if requested
|
270
273
|
if debug:
|
271
274
|
setup_logging(verbose=True)
|
272
275
|
console.print("[yellow]Debug mode enabled[/yellow]")
|
273
|
-
|
276
|
+
|
274
277
|
# Load configuration
|
275
|
-
base_config = ConfigManager.find_config(
|
276
|
-
|
278
|
+
base_config = ConfigManager.find_config("monitor", config)
|
279
|
+
|
277
280
|
if not base_config:
|
278
281
|
# Try to find monitor config in orchestrator config as fallback
|
279
|
-
orch_config = ConfigManager.find_config(
|
280
|
-
if orch_config and
|
281
|
-
base_config = {
|
282
|
+
orch_config = ConfigManager.find_config("orchestrator")
|
283
|
+
if orch_config and "monitor" in orch_config:
|
284
|
+
base_config = {"monitor": orch_config["monitor"]}
|
282
285
|
console.print("[dim]Using monitor config from orchestrator.yaml[/dim]")
|
283
286
|
else:
|
284
287
|
base_config = {}
|
285
288
|
if not server or not token:
|
286
289
|
console.print("[yellow]No monitor config found, using CLI args[/yellow]")
|
287
|
-
|
290
|
+
|
288
291
|
# Handle different config structures
|
289
292
|
# Case 1: Config has top-level 'monitor' section
|
290
|
-
if
|
291
|
-
config_data = base_config[
|
293
|
+
if "monitor" in base_config:
|
294
|
+
config_data = base_config["monitor"]
|
292
295
|
# Case 2: Config IS the monitor config (no wrapper)
|
293
296
|
else:
|
294
297
|
config_data = base_config
|
295
|
-
|
298
|
+
|
296
299
|
# Apply CLI overrides (CLI always wins)
|
297
300
|
if server:
|
298
|
-
config_data[
|
301
|
+
config_data["server"] = server
|
299
302
|
if token:
|
300
|
-
config_data[
|
303
|
+
config_data["token"] = token
|
301
304
|
if no_verify_ssl:
|
302
|
-
config_data[
|
303
|
-
|
305
|
+
config_data["verify_ssl"] = False
|
306
|
+
|
304
307
|
# Debug output
|
305
308
|
if debug:
|
306
309
|
console.print("\n[cyan]Final monitor configuration:[/cyan]")
|
307
310
|
console.print(f" Server: {config_data.get('server', 'NOT SET')}")
|
308
|
-
console.print(
|
311
|
+
console.print(
|
312
|
+
f" Token: {'***' + config_data.get('token', '')[-4:] if config_data.get('token') else 'NOT SET'}"
|
313
|
+
)
|
309
314
|
console.print(f" Verify SSL: {config_data.get('verify_ssl', True)}")
|
310
315
|
console.print()
|
311
|
-
|
316
|
+
|
312
317
|
# Validate required fields
|
313
|
-
if not config_data.get(
|
318
|
+
if not config_data.get("server"):
|
314
319
|
console.print("[red]Error: --server required (or set 'server' in monitor.yaml)[/red]")
|
315
320
|
console.print("\n[dim]Example monitor.yaml:[/dim]")
|
316
321
|
console.print("server: wss://localhost:8765")
|
317
322
|
console.print("token: your-token-here")
|
318
323
|
sys.exit(1)
|
319
|
-
|
320
|
-
if not config_data.get(
|
324
|
+
|
325
|
+
if not config_data.get("token"):
|
321
326
|
console.print("[red]Error: --token required (or set 'token' in monitor.yaml)[/red]")
|
322
327
|
console.print("\n[dim]Example monitor.yaml:[/dim]")
|
323
328
|
console.print("server: wss://localhost:8765")
|
324
329
|
console.print("token: your-token-here")
|
325
330
|
sys.exit(1)
|
326
|
-
|
331
|
+
|
327
332
|
# Set defaults for optional settings
|
328
|
-
config_data.setdefault(
|
329
|
-
config_data.setdefault(
|
330
|
-
config_data.setdefault(
|
331
|
-
|
333
|
+
config_data.setdefault("refresh_interval", 1.0)
|
334
|
+
config_data.setdefault("show_inactive_workers", False)
|
335
|
+
config_data.setdefault("max_log_lines", 100)
|
336
|
+
|
332
337
|
# Create and start monitor
|
333
338
|
try:
|
334
339
|
monitor_instance = Monitor(config_data)
|
335
|
-
|
340
|
+
|
336
341
|
if debug:
|
337
342
|
console.print("[green]Starting monitor...[/green]")
|
338
343
|
console.print(f"[dim]Connecting to: {config_data['server']}[/dim]")
|
339
344
|
sys.exit(1)
|
340
|
-
|
345
|
+
|
341
346
|
asyncio.run(monitor_instance.start())
|
342
|
-
|
347
|
+
|
343
348
|
except KeyboardInterrupt:
|
344
349
|
console.print("\n[yellow]Closing monitor...[/yellow]")
|
345
350
|
except ConnectionRefusedError:
|
@@ -350,9 +355,11 @@ def monitor(ctx, config: Optional[str], server: Optional[str], token: Optional[s
|
|
350
355
|
console.print(f"\n[red]Error starting monitor: {e}[/red]")
|
351
356
|
if debug:
|
352
357
|
import traceback
|
358
|
+
|
353
359
|
traceback.print_exc()
|
354
360
|
sys.exit(1)
|
355
361
|
|
362
|
+
|
356
363
|
@main.command()
|
357
364
|
@click.option("--config", type=click.Path(exists=True), help="Configuration file")
|
358
365
|
@click.option("--server", help="Orchestrator WebSocket URL")
|
@@ -376,11 +383,21 @@ def reload_config(
|
|
376
383
|
if not server or not token:
|
377
384
|
base_config = ConfigManager.find_config("orchestrator", config) or {}
|
378
385
|
admin_config = base_config.get("admin", {})
|
386
|
+
admin_tokens = base_config.get("orchestrator", {}).get("auth", {}).get("admin_tokens", [])
|
387
|
+
has_admin_tokens = False
|
388
|
+
if len(admin_tokens) > 0:
|
389
|
+
has_admin_tokens = True
|
390
|
+
first_admin_token = admin_tokens[0].get("token", None)
|
391
|
+
# Do not print sensitive admin token to console.
|
379
392
|
|
380
393
|
if not server:
|
381
|
-
server = admin_config.get("server")
|
394
|
+
server = admin_config.get("server", "ws://localhost:8765")
|
382
395
|
if not token:
|
383
|
-
token = admin_config.get("token")
|
396
|
+
token = admin_config.get("token", None)
|
397
|
+
if token is None and has_admin_tokens:
|
398
|
+
# grab the first one, we'll just assume we're localhost.
|
399
|
+
console.print("Using first admin token.")
|
400
|
+
token = first_admin_token
|
384
401
|
|
385
402
|
if not server:
|
386
403
|
console.print("[red]Error: --server required (or set in config)[/red]")
|
@@ -447,7 +464,7 @@ def reload_config(
|
|
447
464
|
return True
|
448
465
|
else:
|
449
466
|
error = reload_response.get("error", "Unknown error")
|
450
|
-
console.print(f"[red]Reload failed: {error}[/red]")
|
467
|
+
console.print(f"[red]Reload failed: {error} ({reload_response=})[/red]")
|
451
468
|
return False
|
452
469
|
|
453
470
|
except Exception as e:
|
caption_flow/models.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
"""Data models for CaptionFlow."""
|
2
2
|
|
3
|
-
from dataclasses import dataclass
|
3
|
+
from dataclasses import dataclass, field
|
4
4
|
from datetime import datetime
|
5
5
|
from enum import Enum
|
6
|
-
from typing import Optional
|
6
|
+
from typing import Any, Dict, List, Optional
|
7
7
|
|
8
8
|
|
9
9
|
class JobStatus(Enum):
|
@@ -51,9 +51,10 @@ class Caption:
|
|
51
51
|
timestamp: datetime
|
52
52
|
caption_count: int = 1 # Number of captions generated for this item
|
53
53
|
caption: Optional[str] = None
|
54
|
-
captions: Optional[
|
54
|
+
captions: Optional[List[str]] = None
|
55
|
+
outputs: Dict[str, List[str]] = field(default_factory=dict)
|
55
56
|
quality_score: Optional[float] = None
|
56
|
-
quality_scores: Optional[
|
57
|
+
quality_scores: Optional[List[float]] = None
|
57
58
|
|
58
59
|
# Image metadata
|
59
60
|
image_width: Optional[int] = None
|
@@ -66,6 +67,7 @@ class Caption:
|
|
66
67
|
total_captions: Optional[int] = None # Total captions for this image
|
67
68
|
processing_time_ms: Optional[float] = None
|
68
69
|
chunk_id: Optional[str] = None
|
70
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
69
71
|
|
70
72
|
def __post_init__(self):
|
71
73
|
if self.caption is None and self.captions is None:
|
caption_flow/monitor.py
CHANGED
@@ -166,6 +166,8 @@ class Monitor:
|
|
166
166
|
# Filter out rate stats (already shown in rates panel)
|
167
167
|
for key, value in self.stats.items():
|
168
168
|
if key not in ["current_rate", "average_rate", "expected_rate"]:
|
169
|
+
if isinstance(value, dict):
|
170
|
+
value = json.dumps(value, indent=2)
|
169
171
|
stats_table.add_row(key.replace("_", " ").title(), str(value))
|
170
172
|
|
171
173
|
layout["stats"].update(Panel(stats_table, title="System Statistics", border_style="green"))
|
@@ -178,9 +180,18 @@ class Monitor:
|
|
178
180
|
leaderboard_table.add_column("Trust", style="green")
|
179
181
|
|
180
182
|
for i, contributor in enumerate(self.leaderboard[:10], 1):
|
183
|
+
# Format name with active worker count
|
184
|
+
name = contributor.get("name", "Unknown")
|
185
|
+
active_workers = contributor.get("active_workers", 0)
|
186
|
+
|
187
|
+
if active_workers > 0:
|
188
|
+
name_display = f"{name} [bright_green](x{active_workers})[/bright_green]"
|
189
|
+
else:
|
190
|
+
name_display = f"{name} [dim](offline)[/dim]"
|
191
|
+
|
181
192
|
leaderboard_table.add_row(
|
182
193
|
str(i),
|
183
|
-
|
194
|
+
name_display,
|
184
195
|
str(contributor.get("total_captions", 0)),
|
185
196
|
"⭐" * contributor.get("trust_level", 0),
|
186
197
|
)
|
@@ -188,7 +199,6 @@ class Monitor:
|
|
188
199
|
layout["leaderboard"].update(
|
189
200
|
Panel(leaderboard_table, title="Top Contributors", border_style="yellow")
|
190
201
|
)
|
191
|
-
|
192
202
|
# Activity panel
|
193
203
|
activity_text = Text()
|
194
204
|
for activity in self.recent_activity[-10:]:
|