caption-flow 0.1.0__py3-none-any.whl → 0.2.1__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 +65 -42
- caption_flow/models.py +6 -4
- caption_flow/monitor.py +13 -3
- caption_flow/orchestrator.py +1049 -264
- 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 +567 -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.1.dist-info/METADATA +370 -0
- caption_flow-0.2.1.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.1.dist-info}/WHEEL +0 -0
- {caption_flow-0.1.0.dist-info → caption_flow-0.2.1.dist-info}/entry_points.txt +0 -0
- {caption_flow-0.1.0.dist-info → caption_flow-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {caption_flow-0.1.0.dist-info → caption_flow-0.2.1.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
|
|
@@ -121,13 +120,19 @@ class ConfigManager:
|
|
121
120
|
|
122
121
|
|
123
122
|
def setup_logging(verbose: bool = False):
|
124
|
-
"""Configure logging with rich handler."""
|
123
|
+
"""Configure logging with rich handler, including timestamp."""
|
125
124
|
level = logging.DEBUG if verbose else logging.INFO
|
126
125
|
logging.basicConfig(
|
127
126
|
level=level,
|
128
|
-
format="%(message)s",
|
127
|
+
format="%(asctime)s %(message)s",
|
128
|
+
datefmt="[%Y-%m-%d %H:%M:%S]",
|
129
129
|
handlers=[
|
130
|
-
RichHandler(
|
130
|
+
RichHandler(
|
131
|
+
console=console,
|
132
|
+
rich_tracebacks=True,
|
133
|
+
show_path=False,
|
134
|
+
show_time=True, # Enables timestamp in RichHandler output
|
135
|
+
)
|
131
136
|
],
|
132
137
|
)
|
133
138
|
|
@@ -168,8 +173,6 @@ def orchestrator(ctx, config: Optional[str], **kwargs):
|
|
168
173
|
else:
|
169
174
|
config_data = base_config
|
170
175
|
|
171
|
-
console.print(f"Config contents: {config_data}")
|
172
|
-
|
173
176
|
# Apply CLI overrides
|
174
177
|
if kwargs.get("port"):
|
175
178
|
config_data["port"] = kwargs["port"]
|
@@ -242,11 +245,11 @@ def worker(ctx, config: Optional[str], **kwargs):
|
|
242
245
|
|
243
246
|
# Choose worker type
|
244
247
|
if kwargs.get("vllm") or config_data.get("vllm"):
|
245
|
-
from .
|
248
|
+
from .workers.caption import CaptionWorker
|
246
249
|
|
247
|
-
worker_instance =
|
250
|
+
worker_instance = CaptionWorker(config_data)
|
248
251
|
else:
|
249
|
-
|
252
|
+
raise ValueError(f"Not sure how to handle worker for {config_data.get('type')} type setup.")
|
250
253
|
|
251
254
|
try:
|
252
255
|
asyncio.run(worker_instance.start())
|
@@ -262,84 +265,92 @@ def worker(ctx, config: Optional[str], **kwargs):
|
|
262
265
|
@click.option("--no-verify-ssl", is_flag=True, help="Skip SSL verification")
|
263
266
|
@click.option("--debug", is_flag=True, help="Enable debug output")
|
264
267
|
@click.pass_context
|
265
|
-
def monitor(
|
266
|
-
|
268
|
+
def monitor(
|
269
|
+
ctx,
|
270
|
+
config: Optional[str],
|
271
|
+
server: Optional[str],
|
272
|
+
token: Optional[str],
|
273
|
+
no_verify_ssl: bool,
|
274
|
+
debug: bool,
|
275
|
+
):
|
267
276
|
"""Start the monitoring TUI."""
|
268
|
-
|
277
|
+
|
269
278
|
# Enable debug logging if requested
|
270
279
|
if debug:
|
271
280
|
setup_logging(verbose=True)
|
272
281
|
console.print("[yellow]Debug mode enabled[/yellow]")
|
273
|
-
|
282
|
+
|
274
283
|
# Load configuration
|
275
|
-
base_config = ConfigManager.find_config(
|
276
|
-
|
284
|
+
base_config = ConfigManager.find_config("monitor", config)
|
285
|
+
|
277
286
|
if not base_config:
|
278
287
|
# Try to find monitor config in orchestrator config as fallback
|
279
|
-
orch_config = ConfigManager.find_config(
|
280
|
-
if orch_config and
|
281
|
-
base_config = {
|
288
|
+
orch_config = ConfigManager.find_config("orchestrator")
|
289
|
+
if orch_config and "monitor" in orch_config:
|
290
|
+
base_config = {"monitor": orch_config["monitor"]}
|
282
291
|
console.print("[dim]Using monitor config from orchestrator.yaml[/dim]")
|
283
292
|
else:
|
284
293
|
base_config = {}
|
285
294
|
if not server or not token:
|
286
295
|
console.print("[yellow]No monitor config found, using CLI args[/yellow]")
|
287
|
-
|
296
|
+
|
288
297
|
# Handle different config structures
|
289
298
|
# Case 1: Config has top-level 'monitor' section
|
290
|
-
if
|
291
|
-
config_data = base_config[
|
299
|
+
if "monitor" in base_config:
|
300
|
+
config_data = base_config["monitor"]
|
292
301
|
# Case 2: Config IS the monitor config (no wrapper)
|
293
302
|
else:
|
294
303
|
config_data = base_config
|
295
|
-
|
304
|
+
|
296
305
|
# Apply CLI overrides (CLI always wins)
|
297
306
|
if server:
|
298
|
-
config_data[
|
307
|
+
config_data["server"] = server
|
299
308
|
if token:
|
300
|
-
config_data[
|
309
|
+
config_data["token"] = token
|
301
310
|
if no_verify_ssl:
|
302
|
-
config_data[
|
303
|
-
|
311
|
+
config_data["verify_ssl"] = False
|
312
|
+
|
304
313
|
# Debug output
|
305
314
|
if debug:
|
306
315
|
console.print("\n[cyan]Final monitor configuration:[/cyan]")
|
307
316
|
console.print(f" Server: {config_data.get('server', 'NOT SET')}")
|
308
|
-
console.print(
|
317
|
+
console.print(
|
318
|
+
f" Token: {'***' + config_data.get('token', '')[-4:] if config_data.get('token') else 'NOT SET'}"
|
319
|
+
)
|
309
320
|
console.print(f" Verify SSL: {config_data.get('verify_ssl', True)}")
|
310
321
|
console.print()
|
311
|
-
|
322
|
+
|
312
323
|
# Validate required fields
|
313
|
-
if not config_data.get(
|
324
|
+
if not config_data.get("server"):
|
314
325
|
console.print("[red]Error: --server required (or set 'server' in monitor.yaml)[/red]")
|
315
326
|
console.print("\n[dim]Example monitor.yaml:[/dim]")
|
316
327
|
console.print("server: wss://localhost:8765")
|
317
328
|
console.print("token: your-token-here")
|
318
329
|
sys.exit(1)
|
319
|
-
|
320
|
-
if not config_data.get(
|
330
|
+
|
331
|
+
if not config_data.get("token"):
|
321
332
|
console.print("[red]Error: --token required (or set 'token' in monitor.yaml)[/red]")
|
322
333
|
console.print("\n[dim]Example monitor.yaml:[/dim]")
|
323
334
|
console.print("server: wss://localhost:8765")
|
324
335
|
console.print("token: your-token-here")
|
325
336
|
sys.exit(1)
|
326
|
-
|
337
|
+
|
327
338
|
# Set defaults for optional settings
|
328
|
-
config_data.setdefault(
|
329
|
-
config_data.setdefault(
|
330
|
-
config_data.setdefault(
|
331
|
-
|
339
|
+
config_data.setdefault("refresh_interval", 1.0)
|
340
|
+
config_data.setdefault("show_inactive_workers", False)
|
341
|
+
config_data.setdefault("max_log_lines", 100)
|
342
|
+
|
332
343
|
# Create and start monitor
|
333
344
|
try:
|
334
345
|
monitor_instance = Monitor(config_data)
|
335
|
-
|
346
|
+
|
336
347
|
if debug:
|
337
348
|
console.print("[green]Starting monitor...[/green]")
|
338
349
|
console.print(f"[dim]Connecting to: {config_data['server']}[/dim]")
|
339
350
|
sys.exit(1)
|
340
|
-
|
351
|
+
|
341
352
|
asyncio.run(monitor_instance.start())
|
342
|
-
|
353
|
+
|
343
354
|
except KeyboardInterrupt:
|
344
355
|
console.print("\n[yellow]Closing monitor...[/yellow]")
|
345
356
|
except ConnectionRefusedError:
|
@@ -350,9 +361,11 @@ def monitor(ctx, config: Optional[str], server: Optional[str], token: Optional[s
|
|
350
361
|
console.print(f"\n[red]Error starting monitor: {e}[/red]")
|
351
362
|
if debug:
|
352
363
|
import traceback
|
364
|
+
|
353
365
|
traceback.print_exc()
|
354
366
|
sys.exit(1)
|
355
367
|
|
368
|
+
|
356
369
|
@main.command()
|
357
370
|
@click.option("--config", type=click.Path(exists=True), help="Configuration file")
|
358
371
|
@click.option("--server", help="Orchestrator WebSocket URL")
|
@@ -376,11 +389,21 @@ def reload_config(
|
|
376
389
|
if not server or not token:
|
377
390
|
base_config = ConfigManager.find_config("orchestrator", config) or {}
|
378
391
|
admin_config = base_config.get("admin", {})
|
392
|
+
admin_tokens = base_config.get("orchestrator", {}).get("auth", {}).get("admin_tokens", [])
|
393
|
+
has_admin_tokens = False
|
394
|
+
if len(admin_tokens) > 0:
|
395
|
+
has_admin_tokens = True
|
396
|
+
first_admin_token = admin_tokens[0].get("token", None)
|
397
|
+
# Do not print sensitive admin token to console.
|
379
398
|
|
380
399
|
if not server:
|
381
|
-
server = admin_config.get("server")
|
400
|
+
server = admin_config.get("server", "ws://localhost:8765")
|
382
401
|
if not token:
|
383
|
-
token = admin_config.get("token")
|
402
|
+
token = admin_config.get("token", None)
|
403
|
+
if token is None and has_admin_tokens:
|
404
|
+
# grab the first one, we'll just assume we're localhost.
|
405
|
+
console.print("Using first admin token.")
|
406
|
+
token = first_admin_token
|
384
407
|
|
385
408
|
if not server:
|
386
409
|
console.print("[red]Error: --server required (or set in config)[/red]")
|
@@ -447,7 +470,7 @@ def reload_config(
|
|
447
470
|
return True
|
448
471
|
else:
|
449
472
|
error = reload_response.get("error", "Unknown error")
|
450
|
-
console.print(f"[red]Reload failed: {error}[/red]")
|
473
|
+
console.print(f"[red]Reload failed: {error} ({reload_response=})[/red]")
|
451
474
|
return False
|
452
475
|
|
453
476
|
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
@@ -107,7 +107,7 @@ class Monitor:
|
|
107
107
|
"""Main display update loop."""
|
108
108
|
layout = self._create_layout()
|
109
109
|
|
110
|
-
with Live(layout, console=self.console, refresh_per_second=
|
110
|
+
with Live(layout, console=self.console, refresh_per_second=1, screen=True) as live:
|
111
111
|
while self.running:
|
112
112
|
self._update_layout(layout)
|
113
113
|
await asyncio.sleep(0.25)
|
@@ -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:]:
|