vibe-aigc 0.2.0__tar.gz → 0.4.0__tar.gz

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.
Files changed (47) hide show
  1. {vibe_aigc-0.2.0/vibe_aigc.egg-info → vibe_aigc-0.4.0}/PKG-INFO +2 -1
  2. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/pyproject.toml +5 -1
  3. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/__init__.py +3 -1
  4. vibe_aigc-0.4.0/vibe_aigc/character.py +457 -0
  5. vibe_aigc-0.4.0/vibe_aigc/comfyui.py +447 -0
  6. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/knowledge.py +177 -0
  7. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/llm.py +16 -1
  8. vibe_aigc-0.4.0/vibe_aigc/video.py +388 -0
  9. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0/vibe_aigc.egg-info}/PKG-INFO +2 -1
  10. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc.egg-info/SOURCES.txt +3 -0
  11. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc.egg-info/requires.txt +1 -0
  12. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/LICENSE +0 -0
  13. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/README.md +0 -0
  14. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/setup.cfg +0 -0
  15. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_adaptive_replanning.py +0 -0
  16. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_agents.py +0 -0
  17. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_assets.py +0 -0
  18. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_auto_checkpoint.py +0 -0
  19. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_automatic_checkpoints.py +0 -0
  20. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_checkpoint_serialization.py +0 -0
  21. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_error_handling.py +0 -0
  22. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_executor.py +0 -0
  23. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_feedback_system.py +0 -0
  24. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_integration.py +0 -0
  25. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_knowledge_base.py +0 -0
  26. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_metaplanner_resume.py +0 -0
  27. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_metaplanner_visualization.py +0 -0
  28. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_models.py +0 -0
  29. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_parallel_execution.py +0 -0
  30. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_planner.py +0 -0
  31. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_progress_callbacks.py +0 -0
  32. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_tools.py +0 -0
  33. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_visualization.py +0 -0
  34. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/tests/test_workflow_resume.py +0 -0
  35. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/agents.py +0 -0
  36. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/assets.py +0 -0
  37. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/cli.py +0 -0
  38. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/executor.py +0 -0
  39. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/models.py +0 -0
  40. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/persistence.py +0 -0
  41. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/planner.py +0 -0
  42. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/tools.py +0 -0
  43. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/tools_multimodal.py +0 -0
  44. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc/visualization.py +0 -0
  45. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc.egg-info/dependency_links.txt +0 -0
  46. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc.egg-info/entry_points.txt +0 -0
  47. {vibe_aigc-0.2.0 → vibe_aigc-0.4.0}/vibe_aigc.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vibe-aigc
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: A New Paradigm for Content Generation via Agentic Orchestration
5
5
  Author: Vibe AIGC Contributors
6
6
  License-Expression: MIT
@@ -19,6 +19,7 @@ Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.12
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
+ Requires-Dist: aiohttp>=3.9.0
22
23
  Requires-Dist: pydantic>=2.0.0
23
24
  Requires-Dist: openai>=1.0.0
24
25
  Provides-Extra: dev
@@ -8,7 +8,7 @@ exclude = ["tests*", "docs*", "examples*", "landing*"]
8
8
 
9
9
  [project]
10
10
  name = "vibe-aigc"
11
- version = "0.2.0"
11
+ version = "0.4.0"
12
12
  description = "A New Paradigm for Content Generation via Agentic Orchestration"
13
13
  authors = [{name = "Vibe AIGC Contributors"}]
14
14
  license = "MIT"
@@ -24,6 +24,7 @@ classifiers = [
24
24
  "Typing :: Typed",
25
25
  ]
26
26
  dependencies = [
27
+ "aiohttp>=3.9.0",
27
28
  "pydantic>=2.0.0",
28
29
  "openai>=1.0.0",
29
30
  ]
@@ -65,3 +66,6 @@ python_version = "3.12"
65
66
  warn_return_any = true
66
67
  warn_unused_configs = true
67
68
  ignore_missing_imports = true
69
+
70
+
71
+
@@ -98,4 +98,6 @@ __all__ = [
98
98
  "create_default_agents",
99
99
  # Asset Bank
100
100
  "AssetBank", "Character", "StyleGuide", "Artifact", "create_asset_bank"
101
- ]
101
+ ]
102
+ # ComfyUI backend for actual image generation
103
+ from .comfyui import ComfyUIBackend, ComfyUIConfig, ComfyUIImageTool, create_comfyui_registry
@@ -0,0 +1,457 @@
1
+ """Character consistency using IPAdapter.
2
+
3
+ This enables maintaining character appearance across multiple
4
+ generations - critical for music videos, stories, etc.
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import uuid
10
+ import aiohttp
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional
13
+ from dataclasses import dataclass, field
14
+
15
+ from .comfyui import ComfyUIConfig, GenerationResult
16
+ from .tools import BaseTool, ToolResult, ToolSpec, ToolCategory
17
+
18
+
19
+ @dataclass
20
+ class CharacterReference:
21
+ """A character reference for consistency."""
22
+ name: str
23
+ reference_image: str # Path or URL to reference image
24
+ description: str = ""
25
+ weight: float = 0.8 # How strongly to apply the reference (0-1)
26
+
27
+ def to_dict(self) -> Dict[str, Any]:
28
+ return {
29
+ "name": self.name,
30
+ "reference_image": self.reference_image,
31
+ "description": self.description,
32
+ "weight": self.weight
33
+ }
34
+
35
+
36
+ class CharacterBank:
37
+ """Bank of character references for consistency.
38
+
39
+ Like the paper's Character Bank - maintains character identity
40
+ across multiple generations.
41
+ """
42
+
43
+ def __init__(self):
44
+ self._characters: Dict[str, CharacterReference] = {}
45
+
46
+ def add(self, character: CharacterReference) -> None:
47
+ """Add a character to the bank."""
48
+ self._characters[character.name.lower()] = character
49
+
50
+ def get(self, name: str) -> Optional[CharacterReference]:
51
+ """Get a character by name."""
52
+ return self._characters.get(name.lower())
53
+
54
+ def list_characters(self) -> List[str]:
55
+ """List all character names."""
56
+ return list(self._characters.keys())
57
+
58
+ def remove(self, name: str) -> bool:
59
+ """Remove a character from the bank."""
60
+ if name.lower() in self._characters:
61
+ del self._characters[name.lower()]
62
+ return True
63
+ return False
64
+
65
+
66
+ class IPAdapterBackend:
67
+ """Backend for character-consistent generation using IPAdapter."""
68
+
69
+ def __init__(self, config: Optional[ComfyUIConfig] = None):
70
+ self.config = config or ComfyUIConfig()
71
+ self._client_id = str(uuid.uuid4())
72
+
73
+ async def is_available(self) -> bool:
74
+ """Check if IPAdapter nodes are available."""
75
+ try:
76
+ async with aiohttp.ClientSession() as session:
77
+ async with session.get(
78
+ f"{self.config.base_url}/object_info",
79
+ timeout=aiohttp.ClientTimeout(total=5)
80
+ ) as resp:
81
+ if resp.status != 200:
82
+ return False
83
+ obj_info = await resp.json()
84
+ # Check for IPAdapter nodes
85
+ return "IPAdapterApply" in obj_info or "IPAdapter" in obj_info
86
+ except Exception:
87
+ return False
88
+
89
+ async def generate_with_reference(
90
+ self,
91
+ prompt: str,
92
+ reference_image: str,
93
+ negative_prompt: str = "",
94
+ width: int = 512,
95
+ height: int = 512,
96
+ steps: int = 20,
97
+ cfg: float = 7.0,
98
+ seed: Optional[int] = None,
99
+ reference_weight: float = 0.8,
100
+ checkpoint: str = "v1-5-pruned-emaonly.safetensors"
101
+ ) -> GenerationResult:
102
+ """Generate an image consistent with a reference.
103
+
104
+ Args:
105
+ prompt: Text description
106
+ reference_image: Path to reference image for consistency
107
+ negative_prompt: What to avoid
108
+ width: Output width
109
+ height: Output height
110
+ steps: Sampling steps
111
+ cfg: Guidance scale
112
+ seed: Random seed
113
+ reference_weight: How strongly to apply reference (0-1)
114
+ checkpoint: SD checkpoint
115
+
116
+ Returns:
117
+ GenerationResult with consistent image
118
+ """
119
+ if seed is None:
120
+ import random
121
+ seed = random.randint(0, 2**32 - 1)
122
+
123
+ # First, upload the reference image if it's a local path
124
+ if not reference_image.startswith(('http://', 'https://')):
125
+ reference_image = await self._upload_image(reference_image)
126
+
127
+ workflow = self._build_ipadapter_workflow(
128
+ prompt=prompt,
129
+ reference_image=reference_image,
130
+ negative_prompt=negative_prompt,
131
+ width=width,
132
+ height=height,
133
+ steps=steps,
134
+ cfg=cfg,
135
+ seed=seed,
136
+ reference_weight=reference_weight,
137
+ checkpoint=checkpoint
138
+ )
139
+
140
+ return await self._execute_workflow(workflow)
141
+
142
+ async def _upload_image(self, image_path: str) -> str:
143
+ """Upload a local image to ComfyUI."""
144
+ path = Path(image_path)
145
+ if not path.exists():
146
+ raise FileNotFoundError(f"Reference image not found: {image_path}")
147
+
148
+ async with aiohttp.ClientSession() as session:
149
+ data = aiohttp.FormData()
150
+ data.add_field('image',
151
+ open(path, 'rb'),
152
+ filename=path.name,
153
+ content_type='image/png')
154
+
155
+ async with session.post(
156
+ f"{self.config.base_url}/upload/image",
157
+ data=data
158
+ ) as resp:
159
+ if resp.status != 200:
160
+ raise RuntimeError(f"Failed to upload image: {await resp.text()}")
161
+ result = await resp.json()
162
+ return result.get("name", path.name)
163
+
164
+ def _build_ipadapter_workflow(
165
+ self,
166
+ prompt: str,
167
+ reference_image: str,
168
+ negative_prompt: str,
169
+ width: int,
170
+ height: int,
171
+ steps: int,
172
+ cfg: float,
173
+ seed: int,
174
+ reference_weight: float,
175
+ checkpoint: str
176
+ ) -> Dict[str, Any]:
177
+ """Build IPAdapter workflow for character-consistent generation."""
178
+
179
+ workflow = {
180
+ # Load checkpoint
181
+ "1": {
182
+ "class_type": "CheckpointLoaderSimple",
183
+ "inputs": {
184
+ "ckpt_name": checkpoint
185
+ }
186
+ },
187
+ # Load reference image
188
+ "2": {
189
+ "class_type": "LoadImage",
190
+ "inputs": {
191
+ "image": reference_image
192
+ }
193
+ },
194
+ # Load IPAdapter model
195
+ "3": {
196
+ "class_type": "IPAdapterModelLoader",
197
+ "inputs": {
198
+ "ipadapter_file": "ip-adapter_sd15.safetensors"
199
+ }
200
+ },
201
+ # Load CLIP Vision
202
+ "4": {
203
+ "class_type": "CLIPVisionLoader",
204
+ "inputs": {
205
+ "clip_name": "clip_vision_g.safetensors"
206
+ }
207
+ },
208
+ # Apply IPAdapter
209
+ "5": {
210
+ "class_type": "IPAdapterApply",
211
+ "inputs": {
212
+ "model": ["1", 0],
213
+ "ipadapter": ["3", 0],
214
+ "clip_vision": ["4", 0],
215
+ "image": ["2", 0],
216
+ "weight": reference_weight,
217
+ "noise": 0.0,
218
+ "weight_type": "standard"
219
+ }
220
+ },
221
+ # Positive prompt
222
+ "6": {
223
+ "class_type": "CLIPTextEncode",
224
+ "inputs": {
225
+ "clip": ["1", 1],
226
+ "text": prompt
227
+ }
228
+ },
229
+ # Negative prompt
230
+ "7": {
231
+ "class_type": "CLIPTextEncode",
232
+ "inputs": {
233
+ "clip": ["1", 1],
234
+ "text": negative_prompt
235
+ }
236
+ },
237
+ # Empty latent
238
+ "8": {
239
+ "class_type": "EmptyLatentImage",
240
+ "inputs": {
241
+ "batch_size": 1,
242
+ "height": height,
243
+ "width": width
244
+ }
245
+ },
246
+ # KSampler with IPAdapter-enhanced model
247
+ "9": {
248
+ "class_type": "KSampler",
249
+ "inputs": {
250
+ "cfg": cfg,
251
+ "denoise": 1,
252
+ "latent_image": ["8", 0],
253
+ "model": ["5", 0], # Use IPAdapter-enhanced model
254
+ "negative": ["7", 0],
255
+ "positive": ["6", 0],
256
+ "sampler_name": "euler",
257
+ "scheduler": "normal",
258
+ "seed": seed,
259
+ "steps": steps
260
+ }
261
+ },
262
+ # VAE Decode
263
+ "10": {
264
+ "class_type": "VAEDecode",
265
+ "inputs": {
266
+ "samples": ["9", 0],
267
+ "vae": ["1", 2]
268
+ }
269
+ },
270
+ # Save image
271
+ "11": {
272
+ "class_type": "SaveImage",
273
+ "inputs": {
274
+ "filename_prefix": "vibe_aigc_character",
275
+ "images": ["10", 0]
276
+ }
277
+ }
278
+ }
279
+
280
+ return workflow
281
+
282
+ async def _execute_workflow(self, workflow: Dict[str, Any]) -> GenerationResult:
283
+ """Execute workflow and wait for completion."""
284
+ payload = {
285
+ "prompt": workflow,
286
+ "client_id": self._client_id
287
+ }
288
+
289
+ try:
290
+ async with aiohttp.ClientSession() as session:
291
+ async with session.post(
292
+ f"{self.config.base_url}/prompt",
293
+ json=payload
294
+ ) as resp:
295
+ if resp.status != 200:
296
+ error_text = await resp.text()
297
+ return GenerationResult(
298
+ success=False,
299
+ error=f"Failed to queue prompt: {error_text}"
300
+ )
301
+ result = await resp.json()
302
+ prompt_id = result.get("prompt_id", "")
303
+
304
+ images = await self._wait_for_completion(session, prompt_id)
305
+
306
+ return GenerationResult(
307
+ success=True,
308
+ images=images,
309
+ prompt_id=prompt_id,
310
+ metadata={"type": "character_consistent"}
311
+ )
312
+
313
+ except Exception as e:
314
+ return GenerationResult(
315
+ success=False,
316
+ error=str(e)
317
+ )
318
+
319
+ async def _wait_for_completion(
320
+ self,
321
+ session: aiohttp.ClientSession,
322
+ prompt_id: str,
323
+ timeout: float = 300,
324
+ poll_interval: float = 0.5
325
+ ) -> List[str]:
326
+ """Wait for generation to complete."""
327
+ import time
328
+ start_time = time.time()
329
+
330
+ while time.time() - start_time < timeout:
331
+ async with session.get(f"{self.config.base_url}/history/{prompt_id}") as resp:
332
+ if resp.status == 200:
333
+ history = await resp.json()
334
+
335
+ if prompt_id in history:
336
+ prompt_data = history[prompt_id]
337
+
338
+ if prompt_data.get("status", {}).get("completed", False):
339
+ images = []
340
+ outputs = prompt_data.get("outputs", {})
341
+
342
+ for node_id, node_output in outputs.items():
343
+ if "images" in node_output:
344
+ for img in node_output["images"]:
345
+ filename = img.get("filename")
346
+ subfolder = img.get("subfolder", "")
347
+ if filename:
348
+ url = f"{self.config.base_url}/view?filename={filename}"
349
+ if subfolder:
350
+ url += f"&subfolder={subfolder}"
351
+ images.append(url)
352
+
353
+ return images
354
+
355
+ await asyncio.sleep(poll_interval)
356
+
357
+ raise TimeoutError(f"Generation timed out after {timeout}s")
358
+
359
+
360
+ class CharacterConsistentTool(BaseTool):
361
+ """Tool for generating character-consistent images."""
362
+
363
+ def __init__(self, config: Optional[ComfyUIConfig] = None):
364
+ self.backend = IPAdapterBackend(config)
365
+ self.character_bank = CharacterBank()
366
+ self._spec = ToolSpec(
367
+ name="character_consistent",
368
+ description="Generate images with character consistency using IPAdapter",
369
+ category=ToolCategory.IMAGE,
370
+ input_schema={
371
+ "type": "object",
372
+ "required": ["prompt"],
373
+ "properties": {
374
+ "prompt": {"type": "string"},
375
+ "character_name": {"type": "string", "description": "Name of character from bank"},
376
+ "reference_image": {"type": "string", "description": "Direct path to reference image"},
377
+ "reference_weight": {"type": "number", "default": 0.8},
378
+ "negative_prompt": {"type": "string"},
379
+ "width": {"type": "integer", "default": 512},
380
+ "height": {"type": "integer", "default": 512},
381
+ "steps": {"type": "integer", "default": 20},
382
+ "cfg": {"type": "number", "default": 7.0}
383
+ }
384
+ },
385
+ output_schema={
386
+ "type": "object",
387
+ "properties": {
388
+ "images": {"type": "array", "items": {"type": "string"}},
389
+ "prompt_id": {"type": "string"}
390
+ }
391
+ }
392
+ )
393
+
394
+ @property
395
+ def spec(self) -> ToolSpec:
396
+ return self._spec
397
+
398
+ def add_character(self, character: CharacterReference) -> None:
399
+ """Add a character to the bank."""
400
+ self.character_bank.add(character)
401
+
402
+ async def execute(
403
+ self,
404
+ inputs: Dict[str, Any],
405
+ context: Optional[Dict[str, Any]] = None
406
+ ) -> ToolResult:
407
+ """Execute character-consistent generation."""
408
+ prompt = inputs.get("prompt", "")
409
+ if not prompt:
410
+ return ToolResult(success=False, output=None, error="No prompt provided")
411
+
412
+ # Get reference image
413
+ reference_image = inputs.get("reference_image")
414
+ character_name = inputs.get("character_name")
415
+ reference_weight = inputs.get("reference_weight", 0.8)
416
+
417
+ if character_name:
418
+ char = self.character_bank.get(character_name)
419
+ if char:
420
+ reference_image = char.reference_image
421
+ reference_weight = char.weight
422
+ else:
423
+ return ToolResult(
424
+ success=False,
425
+ output=None,
426
+ error=f"Character '{character_name}' not found in bank"
427
+ )
428
+
429
+ if not reference_image:
430
+ return ToolResult(
431
+ success=False,
432
+ output=None,
433
+ error="No reference_image or character_name provided"
434
+ )
435
+
436
+ result = await self.backend.generate_with_reference(
437
+ prompt=prompt,
438
+ reference_image=reference_image,
439
+ negative_prompt=inputs.get("negative_prompt", ""),
440
+ width=inputs.get("width", 512),
441
+ height=inputs.get("height", 512),
442
+ steps=inputs.get("steps", 20),
443
+ cfg=inputs.get("cfg", 7.0),
444
+ reference_weight=reference_weight
445
+ )
446
+
447
+ if result.success:
448
+ return ToolResult(
449
+ success=True,
450
+ output={
451
+ "images": result.images,
452
+ "prompt_id": result.prompt_id
453
+ },
454
+ metadata=result.metadata
455
+ )
456
+ else:
457
+ return ToolResult(success=False, output=None, error=result.error)