karaoke-gen 0.57.0__py3-none-any.whl → 0.71.27__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.
Files changed (268) hide show
  1. karaoke_gen/audio_fetcher.py +461 -0
  2. karaoke_gen/audio_processor.py +407 -30
  3. karaoke_gen/config.py +62 -113
  4. karaoke_gen/file_handler.py +32 -59
  5. karaoke_gen/karaoke_finalise/karaoke_finalise.py +148 -67
  6. karaoke_gen/karaoke_gen.py +270 -61
  7. karaoke_gen/lyrics_processor.py +13 -1
  8. karaoke_gen/metadata.py +78 -73
  9. karaoke_gen/pipeline/__init__.py +87 -0
  10. karaoke_gen/pipeline/base.py +215 -0
  11. karaoke_gen/pipeline/context.py +230 -0
  12. karaoke_gen/pipeline/executors/__init__.py +21 -0
  13. karaoke_gen/pipeline/executors/local.py +159 -0
  14. karaoke_gen/pipeline/executors/remote.py +257 -0
  15. karaoke_gen/pipeline/stages/__init__.py +27 -0
  16. karaoke_gen/pipeline/stages/finalize.py +202 -0
  17. karaoke_gen/pipeline/stages/render.py +165 -0
  18. karaoke_gen/pipeline/stages/screens.py +139 -0
  19. karaoke_gen/pipeline/stages/separation.py +191 -0
  20. karaoke_gen/pipeline/stages/transcription.py +191 -0
  21. karaoke_gen/style_loader.py +531 -0
  22. karaoke_gen/utils/bulk_cli.py +6 -0
  23. karaoke_gen/utils/cli_args.py +424 -0
  24. karaoke_gen/utils/gen_cli.py +26 -261
  25. karaoke_gen/utils/remote_cli.py +1965 -0
  26. karaoke_gen/video_background_processor.py +351 -0
  27. karaoke_gen-0.71.27.dist-info/METADATA +610 -0
  28. karaoke_gen-0.71.27.dist-info/RECORD +275 -0
  29. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.27.dist-info}/WHEEL +1 -1
  30. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.27.dist-info}/entry_points.txt +1 -0
  31. lyrics_transcriber/__init__.py +10 -0
  32. lyrics_transcriber/cli/__init__.py +0 -0
  33. lyrics_transcriber/cli/cli_main.py +285 -0
  34. lyrics_transcriber/core/__init__.py +0 -0
  35. lyrics_transcriber/core/config.py +50 -0
  36. lyrics_transcriber/core/controller.py +520 -0
  37. lyrics_transcriber/correction/__init__.py +0 -0
  38. lyrics_transcriber/correction/agentic/__init__.py +9 -0
  39. lyrics_transcriber/correction/agentic/adapter.py +71 -0
  40. lyrics_transcriber/correction/agentic/agent.py +313 -0
  41. lyrics_transcriber/correction/agentic/feedback/aggregator.py +12 -0
  42. lyrics_transcriber/correction/agentic/feedback/collector.py +17 -0
  43. lyrics_transcriber/correction/agentic/feedback/retention.py +24 -0
  44. lyrics_transcriber/correction/agentic/feedback/store.py +76 -0
  45. lyrics_transcriber/correction/agentic/handlers/__init__.py +24 -0
  46. lyrics_transcriber/correction/agentic/handlers/ambiguous.py +44 -0
  47. lyrics_transcriber/correction/agentic/handlers/background_vocals.py +68 -0
  48. lyrics_transcriber/correction/agentic/handlers/base.py +51 -0
  49. lyrics_transcriber/correction/agentic/handlers/complex_multi_error.py +46 -0
  50. lyrics_transcriber/correction/agentic/handlers/extra_words.py +74 -0
  51. lyrics_transcriber/correction/agentic/handlers/no_error.py +42 -0
  52. lyrics_transcriber/correction/agentic/handlers/punctuation.py +44 -0
  53. lyrics_transcriber/correction/agentic/handlers/registry.py +60 -0
  54. lyrics_transcriber/correction/agentic/handlers/repeated_section.py +44 -0
  55. lyrics_transcriber/correction/agentic/handlers/sound_alike.py +126 -0
  56. lyrics_transcriber/correction/agentic/models/__init__.py +5 -0
  57. lyrics_transcriber/correction/agentic/models/ai_correction.py +31 -0
  58. lyrics_transcriber/correction/agentic/models/correction_session.py +30 -0
  59. lyrics_transcriber/correction/agentic/models/enums.py +38 -0
  60. lyrics_transcriber/correction/agentic/models/human_feedback.py +30 -0
  61. lyrics_transcriber/correction/agentic/models/learning_data.py +26 -0
  62. lyrics_transcriber/correction/agentic/models/observability_metrics.py +28 -0
  63. lyrics_transcriber/correction/agentic/models/schemas.py +46 -0
  64. lyrics_transcriber/correction/agentic/models/utils.py +19 -0
  65. lyrics_transcriber/correction/agentic/observability/__init__.py +5 -0
  66. lyrics_transcriber/correction/agentic/observability/langfuse_integration.py +35 -0
  67. lyrics_transcriber/correction/agentic/observability/metrics.py +46 -0
  68. lyrics_transcriber/correction/agentic/observability/performance.py +19 -0
  69. lyrics_transcriber/correction/agentic/prompts/__init__.py +2 -0
  70. lyrics_transcriber/correction/agentic/prompts/classifier.py +227 -0
  71. lyrics_transcriber/correction/agentic/providers/__init__.py +6 -0
  72. lyrics_transcriber/correction/agentic/providers/base.py +36 -0
  73. lyrics_transcriber/correction/agentic/providers/circuit_breaker.py +145 -0
  74. lyrics_transcriber/correction/agentic/providers/config.py +73 -0
  75. lyrics_transcriber/correction/agentic/providers/constants.py +24 -0
  76. lyrics_transcriber/correction/agentic/providers/health.py +28 -0
  77. lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +212 -0
  78. lyrics_transcriber/correction/agentic/providers/model_factory.py +209 -0
  79. lyrics_transcriber/correction/agentic/providers/response_cache.py +218 -0
  80. lyrics_transcriber/correction/agentic/providers/response_parser.py +111 -0
  81. lyrics_transcriber/correction/agentic/providers/retry_executor.py +127 -0
  82. lyrics_transcriber/correction/agentic/router.py +35 -0
  83. lyrics_transcriber/correction/agentic/workflows/__init__.py +5 -0
  84. lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py +24 -0
  85. lyrics_transcriber/correction/agentic/workflows/correction_graph.py +59 -0
  86. lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py +24 -0
  87. lyrics_transcriber/correction/anchor_sequence.py +1043 -0
  88. lyrics_transcriber/correction/corrector.py +760 -0
  89. lyrics_transcriber/correction/feedback/__init__.py +2 -0
  90. lyrics_transcriber/correction/feedback/schemas.py +107 -0
  91. lyrics_transcriber/correction/feedback/store.py +236 -0
  92. lyrics_transcriber/correction/handlers/__init__.py +0 -0
  93. lyrics_transcriber/correction/handlers/base.py +52 -0
  94. lyrics_transcriber/correction/handlers/extend_anchor.py +149 -0
  95. lyrics_transcriber/correction/handlers/levenshtein.py +189 -0
  96. lyrics_transcriber/correction/handlers/llm.py +293 -0
  97. lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
  98. lyrics_transcriber/correction/handlers/no_space_punct_match.py +154 -0
  99. lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +85 -0
  100. lyrics_transcriber/correction/handlers/repeat.py +88 -0
  101. lyrics_transcriber/correction/handlers/sound_alike.py +259 -0
  102. lyrics_transcriber/correction/handlers/syllables_match.py +252 -0
  103. lyrics_transcriber/correction/handlers/word_count_match.py +80 -0
  104. lyrics_transcriber/correction/handlers/word_operations.py +187 -0
  105. lyrics_transcriber/correction/operations.py +352 -0
  106. lyrics_transcriber/correction/phrase_analyzer.py +435 -0
  107. lyrics_transcriber/correction/text_utils.py +30 -0
  108. lyrics_transcriber/frontend/.gitignore +23 -0
  109. lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +935 -0
  110. lyrics_transcriber/frontend/.yarnrc.yml +3 -0
  111. lyrics_transcriber/frontend/README.md +50 -0
  112. lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
  113. lyrics_transcriber/frontend/__init__.py +25 -0
  114. lyrics_transcriber/frontend/eslint.config.js +28 -0
  115. lyrics_transcriber/frontend/index.html +18 -0
  116. lyrics_transcriber/frontend/package.json +42 -0
  117. lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
  118. lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
  119. lyrics_transcriber/frontend/public/apple-touch-icon.png +0 -0
  120. lyrics_transcriber/frontend/public/favicon-16x16.png +0 -0
  121. lyrics_transcriber/frontend/public/favicon-32x32.png +0 -0
  122. lyrics_transcriber/frontend/public/favicon.ico +0 -0
  123. lyrics_transcriber/frontend/public/nomad-karaoke-logo.png +0 -0
  124. lyrics_transcriber/frontend/src/App.tsx +212 -0
  125. lyrics_transcriber/frontend/src/api.ts +239 -0
  126. lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +77 -0
  127. lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
  128. lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +204 -0
  129. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +180 -0
  130. lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +167 -0
  131. lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +359 -0
  132. lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +281 -0
  133. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +162 -0
  134. lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +257 -0
  135. lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
  136. lyrics_transcriber/frontend/src/components/EditModal.tsx +702 -0
  137. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +496 -0
  138. lyrics_transcriber/frontend/src/components/EditWordList.tsx +379 -0
  139. lyrics_transcriber/frontend/src/components/FileUpload.tsx +77 -0
  140. lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
  141. lyrics_transcriber/frontend/src/components/Header.tsx +387 -0
  142. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +1373 -0
  143. lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +51 -0
  144. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +67 -0
  145. lyrics_transcriber/frontend/src/components/ModelSelector.tsx +23 -0
  146. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +144 -0
  147. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +268 -0
  148. lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +688 -0
  149. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +354 -0
  150. lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
  151. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +376 -0
  152. lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +131 -0
  153. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +256 -0
  154. lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
  155. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +379 -0
  156. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +56 -0
  157. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +87 -0
  158. lyrics_transcriber/frontend/src/components/shared/constants.ts +20 -0
  159. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +180 -0
  160. lyrics_transcriber/frontend/src/components/shared/styles.ts +13 -0
  161. lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
  162. lyrics_transcriber/frontend/src/components/shared/types.ts +129 -0
  163. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +177 -0
  164. lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
  165. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +75 -0
  166. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +360 -0
  167. lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +110 -0
  168. lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
  169. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +435 -0
  170. lyrics_transcriber/frontend/src/main.tsx +17 -0
  171. lyrics_transcriber/frontend/src/theme.ts +177 -0
  172. lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
  173. lyrics_transcriber/frontend/src/types.js +2 -0
  174. lyrics_transcriber/frontend/src/types.ts +199 -0
  175. lyrics_transcriber/frontend/src/validation.ts +132 -0
  176. lyrics_transcriber/frontend/src/vite-env.d.ts +1 -0
  177. lyrics_transcriber/frontend/tsconfig.app.json +26 -0
  178. lyrics_transcriber/frontend/tsconfig.json +25 -0
  179. lyrics_transcriber/frontend/tsconfig.node.json +23 -0
  180. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -0
  181. lyrics_transcriber/frontend/update_version.js +11 -0
  182. lyrics_transcriber/frontend/vite.config.d.ts +2 -0
  183. lyrics_transcriber/frontend/vite.config.js +10 -0
  184. lyrics_transcriber/frontend/vite.config.ts +11 -0
  185. lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
  186. lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
  187. lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
  188. lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js +42039 -0
  189. lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js.map +1 -0
  190. lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
  191. lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
  192. lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
  193. lyrics_transcriber/frontend/web_assets/index.html +18 -0
  194. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
  195. lyrics_transcriber/frontend/yarn.lock +3752 -0
  196. lyrics_transcriber/lyrics/__init__.py +0 -0
  197. lyrics_transcriber/lyrics/base_lyrics_provider.py +211 -0
  198. lyrics_transcriber/lyrics/file_provider.py +95 -0
  199. lyrics_transcriber/lyrics/genius.py +384 -0
  200. lyrics_transcriber/lyrics/lrclib.py +231 -0
  201. lyrics_transcriber/lyrics/musixmatch.py +156 -0
  202. lyrics_transcriber/lyrics/spotify.py +290 -0
  203. lyrics_transcriber/lyrics/user_input_provider.py +44 -0
  204. lyrics_transcriber/output/__init__.py +0 -0
  205. lyrics_transcriber/output/ass/__init__.py +21 -0
  206. lyrics_transcriber/output/ass/ass.py +2088 -0
  207. lyrics_transcriber/output/ass/ass_specs.txt +732 -0
  208. lyrics_transcriber/output/ass/config.py +180 -0
  209. lyrics_transcriber/output/ass/constants.py +23 -0
  210. lyrics_transcriber/output/ass/event.py +94 -0
  211. lyrics_transcriber/output/ass/formatters.py +132 -0
  212. lyrics_transcriber/output/ass/lyrics_line.py +265 -0
  213. lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
  214. lyrics_transcriber/output/ass/section_detector.py +89 -0
  215. lyrics_transcriber/output/ass/section_screen.py +106 -0
  216. lyrics_transcriber/output/ass/style.py +187 -0
  217. lyrics_transcriber/output/cdg.py +619 -0
  218. lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
  219. lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
  220. lyrics_transcriber/output/cdgmaker/composer.py +2260 -0
  221. lyrics_transcriber/output/cdgmaker/config.py +151 -0
  222. lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
  223. lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
  224. lyrics_transcriber/output/cdgmaker/pack.py +507 -0
  225. lyrics_transcriber/output/cdgmaker/render.py +346 -0
  226. lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
  227. lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
  228. lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
  229. lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
  230. lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
  231. lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
  232. lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
  233. lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
  234. lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
  235. lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
  236. lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
  237. lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
  238. lyrics_transcriber/output/cdgmaker/utils.py +132 -0
  239. lyrics_transcriber/output/countdown_processor.py +267 -0
  240. lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
  241. lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
  242. lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
  243. lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
  244. lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
  245. lyrics_transcriber/output/fonts/arial.ttf +0 -0
  246. lyrics_transcriber/output/fonts/georgia.ttf +0 -0
  247. lyrics_transcriber/output/fonts/verdana.ttf +0 -0
  248. lyrics_transcriber/output/generator.py +257 -0
  249. lyrics_transcriber/output/lrc_to_cdg.py +61 -0
  250. lyrics_transcriber/output/lyrics_file.py +102 -0
  251. lyrics_transcriber/output/plain_text.py +96 -0
  252. lyrics_transcriber/output/segment_resizer.py +431 -0
  253. lyrics_transcriber/output/subtitles.py +397 -0
  254. lyrics_transcriber/output/video.py +544 -0
  255. lyrics_transcriber/review/__init__.py +0 -0
  256. lyrics_transcriber/review/server.py +676 -0
  257. lyrics_transcriber/storage/__init__.py +0 -0
  258. lyrics_transcriber/storage/dropbox.py +225 -0
  259. lyrics_transcriber/transcribers/__init__.py +0 -0
  260. lyrics_transcriber/transcribers/audioshake.py +290 -0
  261. lyrics_transcriber/transcribers/base_transcriber.py +157 -0
  262. lyrics_transcriber/transcribers/whisper.py +330 -0
  263. lyrics_transcriber/types.py +648 -0
  264. lyrics_transcriber/utils/__init__.py +0 -0
  265. lyrics_transcriber/utils/word_utils.py +27 -0
  266. karaoke_gen-0.57.0.dist-info/METADATA +0 -167
  267. karaoke_gen-0.57.0.dist-info/RECORD +0 -23
  268. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.27.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,111 @@
1
+ """Parser for LLM responses into structured correction proposals."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import logging
6
+ from typing import List, Dict, Any
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class ResponseParser:
12
+ """Parses LLM responses into structured proposal dictionaries.
13
+
14
+ Handles both JSON and raw text responses, providing consistent
15
+ output format for downstream processing.
16
+
17
+ Single Responsibility: Response parsing only, no model invocation.
18
+ """
19
+
20
+ def parse(self, content: str) -> List[Dict[str, Any]]:
21
+ """Parse response content into proposal dictionaries.
22
+
23
+ Attempts to parse as JSON first. If that fails, tries to fix
24
+ common JSON issues and retries. Falls back to raw content.
25
+
26
+ Args:
27
+ content: Raw response content from LLM
28
+
29
+ Returns:
30
+ List of proposal dictionaries. On parse failure, returns
31
+ [{"raw": content}] to preserve the response.
32
+ """
33
+ # Try JSON parsing first
34
+ try:
35
+ data = json.loads(content)
36
+ return self._normalize_json_response(data)
37
+ except json.JSONDecodeError as e:
38
+ logger.debug(f"🤖 Response is not valid JSON: {e}")
39
+
40
+ # Try to fix common issues
41
+ fixed_content = self._attempt_json_fix(content)
42
+ if fixed_content != content:
43
+ try:
44
+ data = json.loads(fixed_content)
45
+ logger.debug("🤖 Successfully parsed after JSON fix")
46
+ return self._normalize_json_response(data)
47
+ except json.JSONDecodeError:
48
+ pass # Fall through to raw handling
49
+
50
+ return self._handle_raw_response(content)
51
+
52
+ def _attempt_json_fix(self, content: str) -> str:
53
+ """Attempt to fix common JSON formatting issues.
54
+
55
+ Args:
56
+ content: Raw JSON string
57
+
58
+ Returns:
59
+ Fixed JSON string (or original if no fixes applied)
60
+ """
61
+ # Fix 1: Replace invalid escape sequences like \' with '
62
+ # (JSON only allows \", \\, \/, \b, \f, \n, \r, \t)
63
+ fixed = content.replace("\\'", "'")
64
+
65
+ # Fix 2: Remove any trailing commas before } or ]
66
+ import re
67
+ fixed = re.sub(r',\s*}', '}', fixed)
68
+ fixed = re.sub(r',\s*]', ']', fixed)
69
+
70
+ return fixed
71
+
72
+ def _normalize_json_response(self, data: Any) -> List[Dict[str, Any]]:
73
+ """Normalize JSON data into a list of dictionaries.
74
+
75
+ Handles both single dict and list of dicts responses.
76
+
77
+ Args:
78
+ data: Parsed JSON data
79
+
80
+ Returns:
81
+ List of dictionaries
82
+ """
83
+ if isinstance(data, dict):
84
+ # Single proposal - wrap in list
85
+ return [data]
86
+ elif isinstance(data, list):
87
+ # Already a list - return as-is
88
+ return data
89
+ else:
90
+ # Unexpected type - wrap in error dict
91
+ logger.warning(f"🤖 Unexpected JSON type: {type(data)}")
92
+ return [{"error": "unexpected_type", "data": str(data)}]
93
+
94
+ def _handle_raw_response(self, content: str) -> List[Dict[str, Any]]:
95
+ """Handle non-JSON responses.
96
+
97
+ Wraps raw content in a dict for downstream handling.
98
+ The "raw" key indicates this needs manual processing.
99
+
100
+ Args:
101
+ content: Raw response text
102
+
103
+ Returns:
104
+ List with single dict containing raw content
105
+ """
106
+ logger.info(
107
+ f"🤖 Returning raw response (non-JSON): "
108
+ f"{content[:100]}{'...' if len(content) > 100 else ''}"
109
+ )
110
+ return [{"raw": content}]
111
+
@@ -0,0 +1,127 @@
1
+ """Retry execution logic with exponential backoff."""
2
+ from __future__ import annotations
3
+
4
+ import time
5
+ import random
6
+ import logging
7
+ from typing import Callable, TypeVar, Generic
8
+ from dataclasses import dataclass
9
+
10
+ from .config import ProviderConfig
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ T = TypeVar('T')
15
+
16
+
17
+ @dataclass
18
+ class ExecutionResult(Generic[T]):
19
+ """Result of a retry execution attempt.
20
+
21
+ Attributes:
22
+ success: Whether execution succeeded
23
+ value: The return value if successful
24
+ error: Error message if failed
25
+ attempts: Number of attempts made
26
+ """
27
+ success: bool
28
+ value: T | None = None
29
+ error: str | None = None
30
+ attempts: int = 0
31
+
32
+
33
+ class RetryExecutor:
34
+ """Executes operations with retry logic and exponential backoff.
35
+
36
+ Implements exponential backoff with jitter to prevent thundering herd.
37
+
38
+ Single Responsibility: Retry logic only, no model-specific behavior.
39
+ """
40
+
41
+ def __init__(self, config: ProviderConfig):
42
+ """Initialize retry executor with configuration.
43
+
44
+ Args:
45
+ config: Provider configuration with retry parameters
46
+ """
47
+ self._config = config
48
+
49
+ def execute_with_retry(
50
+ self,
51
+ operation: Callable[[], T],
52
+ operation_name: str = "operation"
53
+ ) -> ExecutionResult[T]:
54
+ """Execute operation with retry logic.
55
+
56
+ Args:
57
+ operation: Callable that performs the operation
58
+ operation_name: Name for logging purposes
59
+
60
+ Returns:
61
+ ExecutionResult with success/failure status and value/error
62
+ """
63
+ max_attempts = max(1, self._config.max_retries + 1)
64
+ last_error: Exception | None = None
65
+
66
+ for attempt in range(max_attempts):
67
+ try:
68
+ logger.debug(
69
+ f"🤖 Executing {operation_name} "
70
+ f"(attempt {attempt + 1}/{max_attempts})"
71
+ )
72
+
73
+ result = operation()
74
+
75
+ logger.debug(f"🤖 {operation_name} succeeded on attempt {attempt + 1}")
76
+ return ExecutionResult(
77
+ success=True,
78
+ value=result,
79
+ attempts=attempt + 1
80
+ )
81
+
82
+ except Exception as e:
83
+ last_error = e
84
+ logger.warning(
85
+ f"🤖 {operation_name} failed on attempt {attempt + 1}: {e}"
86
+ )
87
+
88
+ # Don't sleep after the last attempt
89
+ if attempt < max_attempts - 1:
90
+ sleep_duration = self._calculate_backoff(attempt)
91
+ logger.debug(f"🤖 Backing off for {sleep_duration:.2f}s")
92
+ time.sleep(sleep_duration)
93
+
94
+ # All attempts failed
95
+ error_msg = str(last_error) if last_error else "unknown error"
96
+ logger.error(
97
+ f"🤖 {operation_name} failed after {max_attempts} attempts: {error_msg}"
98
+ )
99
+
100
+ return ExecutionResult(
101
+ success=False,
102
+ error=error_msg,
103
+ attempts=max_attempts
104
+ )
105
+
106
+ def _calculate_backoff(self, attempt: int) -> float:
107
+ """Calculate backoff duration with exponential backoff and jitter.
108
+
109
+ Formula: base * (factor ^ attempt) + random_jitter
110
+
111
+ Args:
112
+ attempt: Current attempt number (0-indexed)
113
+
114
+ Returns:
115
+ Sleep duration in seconds
116
+ """
117
+ base = self._config.retry_backoff_base_seconds
118
+ factor = self._config.retry_backoff_factor
119
+
120
+ # Exponential backoff
121
+ backoff = base * (factor ** attempt)
122
+
123
+ # Add jitter (0-50ms) to prevent thundering herd
124
+ jitter = random.uniform(0, 0.05)
125
+
126
+ return backoff + jitter
127
+
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import Dict, Any
5
+
6
+ from .providers.config import ProviderConfig
7
+
8
+
9
+ class ModelRouter:
10
+ """Rules-based routing by gap type/length/uncertainty (scaffold)."""
11
+
12
+ def __init__(self, config: ProviderConfig | None = None):
13
+ self._config = config or ProviderConfig.from_env()
14
+
15
+ def choose_model(self, gap_type: str, uncertainty: float) -> str:
16
+ """Choose appropriate model based on gap characteristics.
17
+
18
+ Returns model identifier in format "provider/model" for LangChain:
19
+ - "ollama/gpt-oss:latest" for local Ollama models
20
+ - "openai/gpt-4" for OpenAI models
21
+ - "anthropic/claude-3-sonnet-20240229" for Anthropic models
22
+ """
23
+ # Simple baseline per technical guidance
24
+ if self._config.privacy_mode:
25
+ # Use the actual model from env, or default to a common Ollama model
26
+ return os.getenv("AGENTIC_AI_MODEL", "ollama/gpt-oss:latest")
27
+
28
+ # For high-uncertainty gaps, use Claude (best reasoning)
29
+ if uncertainty > 0.5:
30
+ return "anthropic/claude-3-sonnet-20240229"
31
+
32
+ # Default to GPT-4 for general cases
33
+ return "openai/gpt-4"
34
+
35
+
@@ -0,0 +1,5 @@
1
+ """LangGraph workflows for agentic correction (scaffold)."""
2
+
3
+ __all__ = []
4
+
5
+
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict
4
+
5
+
6
+ def build_consensus_workflow() -> Any:
7
+ """Return a minimal consensus workflow (scaffold).
8
+
9
+ Returns None if langgraph not installed to avoid hard dependency.
10
+ """
11
+ try:
12
+ from langgraph.graph import StateGraph # type: ignore
13
+ except Exception:
14
+ return None
15
+
16
+ def merge_results(state: Dict[str, Any]) -> Dict[str, Any]:
17
+ return state
18
+
19
+ g = StateGraph(dict)
20
+ g.add_node("MergeResults", merge_results)
21
+ g.set_entry_point("MergeResults")
22
+ return g.compile()
23
+
24
+
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, Any, List, Annotated
4
+ from typing_extensions import TypedDict
5
+
6
+
7
+ class CorrectionState(TypedDict):
8
+ """State for the correction workflow.
9
+
10
+ This is a minimal state for now, but can be expanded as we add
11
+ more sophisticated correction logic (e.g., multi-step reasoning,
12
+ validation loops, etc.)
13
+ """
14
+ prompt: str
15
+ proposals: List[Dict[str, Any]]
16
+
17
+
18
+ def build_correction_graph(callbacks=None) -> Any:
19
+ """Build a LangGraph workflow for lyrics correction.
20
+
21
+ Currently a simple pass-through, but structured to allow future
22
+ expansion with multi-step reasoning, validation loops, etc.
23
+
24
+ Args:
25
+ callbacks: Optional callbacks (e.g., Langfuse handlers) to attach
26
+
27
+ Returns:
28
+ Compiled LangGraph or None if LangGraph not installed
29
+ """
30
+ try:
31
+ from langgraph.graph import StateGraph, END
32
+ except ImportError:
33
+ return None
34
+
35
+ def correction_node(state: CorrectionState) -> CorrectionState:
36
+ """Main correction node - currently a pass-through.
37
+
38
+ Future expansion: This could invoke sub-agents, do multi-step
39
+ reasoning, or implement validation loops.
40
+ """
41
+ # For now, just pass through - actual correction happens in provider
42
+ return state
43
+
44
+ # Build the graph
45
+ graph_builder = StateGraph(CorrectionState)
46
+ graph_builder.add_node("correct", correction_node)
47
+ graph_builder.set_entry_point("correct")
48
+ graph_builder.set_finish_point("correct")
49
+
50
+ # Compile with optional callbacks
51
+ # Note: Per Langfuse docs, we can use .with_config() to add callbacks
52
+ compiled = graph_builder.compile()
53
+
54
+ if callbacks:
55
+ return compiled.with_config({"callbacks": callbacks})
56
+
57
+ return compiled
58
+
59
+
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict
4
+
5
+
6
+ def build_feedback_workflow() -> Any:
7
+ """Return a minimal feedback processing workflow (scaffold).
8
+
9
+ Returns None if langgraph not installed to avoid hard dependency.
10
+ """
11
+ try:
12
+ from langgraph.graph import StateGraph # type: ignore
13
+ except Exception:
14
+ return None
15
+
16
+ def process_feedback(state: Dict[str, Any]) -> Dict[str, Any]:
17
+ return state
18
+
19
+ g = StateGraph(dict)
20
+ g.add_node("ProcessFeedback", process_feedback)
21
+ g.set_entry_point("ProcessFeedback")
22
+ return g.compile()
23
+
24
+