karaoke-gen 0.57.0__py3-none-any.whl → 0.71.23__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.
- karaoke_gen/audio_fetcher.py +461 -0
- karaoke_gen/audio_processor.py +407 -30
- karaoke_gen/config.py +62 -113
- karaoke_gen/file_handler.py +32 -59
- karaoke_gen/karaoke_finalise/karaoke_finalise.py +148 -67
- karaoke_gen/karaoke_gen.py +270 -61
- karaoke_gen/lyrics_processor.py +13 -1
- karaoke_gen/metadata.py +78 -73
- karaoke_gen/pipeline/__init__.py +87 -0
- karaoke_gen/pipeline/base.py +215 -0
- karaoke_gen/pipeline/context.py +230 -0
- karaoke_gen/pipeline/executors/__init__.py +21 -0
- karaoke_gen/pipeline/executors/local.py +159 -0
- karaoke_gen/pipeline/executors/remote.py +257 -0
- karaoke_gen/pipeline/stages/__init__.py +27 -0
- karaoke_gen/pipeline/stages/finalize.py +202 -0
- karaoke_gen/pipeline/stages/render.py +165 -0
- karaoke_gen/pipeline/stages/screens.py +139 -0
- karaoke_gen/pipeline/stages/separation.py +191 -0
- karaoke_gen/pipeline/stages/transcription.py +191 -0
- karaoke_gen/style_loader.py +531 -0
- karaoke_gen/utils/bulk_cli.py +6 -0
- karaoke_gen/utils/cli_args.py +424 -0
- karaoke_gen/utils/gen_cli.py +26 -261
- karaoke_gen/utils/remote_cli.py +1815 -0
- karaoke_gen/video_background_processor.py +351 -0
- karaoke_gen-0.71.23.dist-info/METADATA +610 -0
- karaoke_gen-0.71.23.dist-info/RECORD +275 -0
- {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info}/WHEEL +1 -1
- {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info}/entry_points.txt +1 -0
- lyrics_transcriber/__init__.py +10 -0
- lyrics_transcriber/cli/__init__.py +0 -0
- lyrics_transcriber/cli/cli_main.py +285 -0
- lyrics_transcriber/core/__init__.py +0 -0
- lyrics_transcriber/core/config.py +50 -0
- lyrics_transcriber/core/controller.py +520 -0
- lyrics_transcriber/correction/__init__.py +0 -0
- lyrics_transcriber/correction/agentic/__init__.py +9 -0
- lyrics_transcriber/correction/agentic/adapter.py +71 -0
- lyrics_transcriber/correction/agentic/agent.py +313 -0
- lyrics_transcriber/correction/agentic/feedback/aggregator.py +12 -0
- lyrics_transcriber/correction/agentic/feedback/collector.py +17 -0
- lyrics_transcriber/correction/agentic/feedback/retention.py +24 -0
- lyrics_transcriber/correction/agentic/feedback/store.py +76 -0
- lyrics_transcriber/correction/agentic/handlers/__init__.py +24 -0
- lyrics_transcriber/correction/agentic/handlers/ambiguous.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/background_vocals.py +68 -0
- lyrics_transcriber/correction/agentic/handlers/base.py +51 -0
- lyrics_transcriber/correction/agentic/handlers/complex_multi_error.py +46 -0
- lyrics_transcriber/correction/agentic/handlers/extra_words.py +74 -0
- lyrics_transcriber/correction/agentic/handlers/no_error.py +42 -0
- lyrics_transcriber/correction/agentic/handlers/punctuation.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/registry.py +60 -0
- lyrics_transcriber/correction/agentic/handlers/repeated_section.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/sound_alike.py +126 -0
- lyrics_transcriber/correction/agentic/models/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/models/ai_correction.py +31 -0
- lyrics_transcriber/correction/agentic/models/correction_session.py +30 -0
- lyrics_transcriber/correction/agentic/models/enums.py +38 -0
- lyrics_transcriber/correction/agentic/models/human_feedback.py +30 -0
- lyrics_transcriber/correction/agentic/models/learning_data.py +26 -0
- lyrics_transcriber/correction/agentic/models/observability_metrics.py +28 -0
- lyrics_transcriber/correction/agentic/models/schemas.py +46 -0
- lyrics_transcriber/correction/agentic/models/utils.py +19 -0
- lyrics_transcriber/correction/agentic/observability/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/observability/langfuse_integration.py +35 -0
- lyrics_transcriber/correction/agentic/observability/metrics.py +46 -0
- lyrics_transcriber/correction/agentic/observability/performance.py +19 -0
- lyrics_transcriber/correction/agentic/prompts/__init__.py +2 -0
- lyrics_transcriber/correction/agentic/prompts/classifier.py +227 -0
- lyrics_transcriber/correction/agentic/providers/__init__.py +6 -0
- lyrics_transcriber/correction/agentic/providers/base.py +36 -0
- lyrics_transcriber/correction/agentic/providers/circuit_breaker.py +145 -0
- lyrics_transcriber/correction/agentic/providers/config.py +73 -0
- lyrics_transcriber/correction/agentic/providers/constants.py +24 -0
- lyrics_transcriber/correction/agentic/providers/health.py +28 -0
- lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +212 -0
- lyrics_transcriber/correction/agentic/providers/model_factory.py +209 -0
- lyrics_transcriber/correction/agentic/providers/response_cache.py +218 -0
- lyrics_transcriber/correction/agentic/providers/response_parser.py +111 -0
- lyrics_transcriber/correction/agentic/providers/retry_executor.py +127 -0
- lyrics_transcriber/correction/agentic/router.py +35 -0
- lyrics_transcriber/correction/agentic/workflows/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py +24 -0
- lyrics_transcriber/correction/agentic/workflows/correction_graph.py +59 -0
- lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py +24 -0
- lyrics_transcriber/correction/anchor_sequence.py +1043 -0
- lyrics_transcriber/correction/corrector.py +760 -0
- lyrics_transcriber/correction/feedback/__init__.py +2 -0
- lyrics_transcriber/correction/feedback/schemas.py +107 -0
- lyrics_transcriber/correction/feedback/store.py +236 -0
- lyrics_transcriber/correction/handlers/__init__.py +0 -0
- lyrics_transcriber/correction/handlers/base.py +52 -0
- lyrics_transcriber/correction/handlers/extend_anchor.py +149 -0
- lyrics_transcriber/correction/handlers/levenshtein.py +189 -0
- lyrics_transcriber/correction/handlers/llm.py +293 -0
- lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
- lyrics_transcriber/correction/handlers/no_space_punct_match.py +154 -0
- lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +85 -0
- lyrics_transcriber/correction/handlers/repeat.py +88 -0
- lyrics_transcriber/correction/handlers/sound_alike.py +259 -0
- lyrics_transcriber/correction/handlers/syllables_match.py +252 -0
- lyrics_transcriber/correction/handlers/word_count_match.py +80 -0
- lyrics_transcriber/correction/handlers/word_operations.py +187 -0
- lyrics_transcriber/correction/operations.py +352 -0
- lyrics_transcriber/correction/phrase_analyzer.py +435 -0
- lyrics_transcriber/correction/text_utils.py +30 -0
- lyrics_transcriber/frontend/.gitignore +23 -0
- lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +935 -0
- lyrics_transcriber/frontend/.yarnrc.yml +3 -0
- lyrics_transcriber/frontend/README.md +50 -0
- lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
- lyrics_transcriber/frontend/__init__.py +25 -0
- lyrics_transcriber/frontend/eslint.config.js +28 -0
- lyrics_transcriber/frontend/index.html +18 -0
- lyrics_transcriber/frontend/package.json +42 -0
- lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/public/apple-touch-icon.png +0 -0
- lyrics_transcriber/frontend/public/favicon-16x16.png +0 -0
- lyrics_transcriber/frontend/public/favicon-32x32.png +0 -0
- lyrics_transcriber/frontend/public/favicon.ico +0 -0
- lyrics_transcriber/frontend/public/nomad-karaoke-logo.png +0 -0
- lyrics_transcriber/frontend/src/App.tsx +212 -0
- lyrics_transcriber/frontend/src/api.ts +239 -0
- lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +77 -0
- lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
- lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +204 -0
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +180 -0
- lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +167 -0
- lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +359 -0
- lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +281 -0
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +162 -0
- lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +257 -0
- lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
- lyrics_transcriber/frontend/src/components/EditModal.tsx +702 -0
- lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +496 -0
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +379 -0
- lyrics_transcriber/frontend/src/components/FileUpload.tsx +77 -0
- lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
- lyrics_transcriber/frontend/src/components/Header.tsx +387 -0
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +1373 -0
- lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +51 -0
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +67 -0
- lyrics_transcriber/frontend/src/components/ModelSelector.tsx +23 -0
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +144 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +268 -0
- lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +688 -0
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +354 -0
- lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +376 -0
- lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +131 -0
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +256 -0
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +379 -0
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +56 -0
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +87 -0
- lyrics_transcriber/frontend/src/components/shared/constants.ts +20 -0
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +180 -0
- lyrics_transcriber/frontend/src/components/shared/styles.ts +13 -0
- lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
- lyrics_transcriber/frontend/src/components/shared/types.ts +129 -0
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +177 -0
- lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +75 -0
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +360 -0
- lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +110 -0
- lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +435 -0
- lyrics_transcriber/frontend/src/main.tsx +17 -0
- lyrics_transcriber/frontend/src/theme.ts +177 -0
- lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
- lyrics_transcriber/frontend/src/types.js +2 -0
- lyrics_transcriber/frontend/src/types.ts +199 -0
- lyrics_transcriber/frontend/src/validation.ts +132 -0
- lyrics_transcriber/frontend/src/vite-env.d.ts +1 -0
- lyrics_transcriber/frontend/tsconfig.app.json +26 -0
- lyrics_transcriber/frontend/tsconfig.json +25 -0
- lyrics_transcriber/frontend/tsconfig.node.json +23 -0
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -0
- lyrics_transcriber/frontend/update_version.js +11 -0
- lyrics_transcriber/frontend/vite.config.d.ts +2 -0
- lyrics_transcriber/frontend/vite.config.js +10 -0
- lyrics_transcriber/frontend/vite.config.ts +11 -0
- lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
- lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js +42039 -0
- lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js.map +1 -0
- lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
- lyrics_transcriber/frontend/web_assets/index.html +18 -0
- lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
- lyrics_transcriber/frontend/yarn.lock +3752 -0
- lyrics_transcriber/lyrics/__init__.py +0 -0
- lyrics_transcriber/lyrics/base_lyrics_provider.py +211 -0
- lyrics_transcriber/lyrics/file_provider.py +95 -0
- lyrics_transcriber/lyrics/genius.py +384 -0
- lyrics_transcriber/lyrics/lrclib.py +231 -0
- lyrics_transcriber/lyrics/musixmatch.py +156 -0
- lyrics_transcriber/lyrics/spotify.py +290 -0
- lyrics_transcriber/lyrics/user_input_provider.py +44 -0
- lyrics_transcriber/output/__init__.py +0 -0
- lyrics_transcriber/output/ass/__init__.py +21 -0
- lyrics_transcriber/output/ass/ass.py +2088 -0
- lyrics_transcriber/output/ass/ass_specs.txt +732 -0
- lyrics_transcriber/output/ass/config.py +180 -0
- lyrics_transcriber/output/ass/constants.py +23 -0
- lyrics_transcriber/output/ass/event.py +94 -0
- lyrics_transcriber/output/ass/formatters.py +132 -0
- lyrics_transcriber/output/ass/lyrics_line.py +265 -0
- lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
- lyrics_transcriber/output/ass/section_detector.py +89 -0
- lyrics_transcriber/output/ass/section_screen.py +106 -0
- lyrics_transcriber/output/ass/style.py +187 -0
- lyrics_transcriber/output/cdg.py +619 -0
- lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
- lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
- lyrics_transcriber/output/cdgmaker/composer.py +2260 -0
- lyrics_transcriber/output/cdgmaker/config.py +151 -0
- lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
- lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
- lyrics_transcriber/output/cdgmaker/pack.py +507 -0
- lyrics_transcriber/output/cdgmaker/render.py +346 -0
- lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
- lyrics_transcriber/output/cdgmaker/utils.py +132 -0
- lyrics_transcriber/output/countdown_processor.py +267 -0
- lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
- lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
- lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/arial.ttf +0 -0
- lyrics_transcriber/output/fonts/georgia.ttf +0 -0
- lyrics_transcriber/output/fonts/verdana.ttf +0 -0
- lyrics_transcriber/output/generator.py +257 -0
- lyrics_transcriber/output/lrc_to_cdg.py +61 -0
- lyrics_transcriber/output/lyrics_file.py +102 -0
- lyrics_transcriber/output/plain_text.py +96 -0
- lyrics_transcriber/output/segment_resizer.py +431 -0
- lyrics_transcriber/output/subtitles.py +397 -0
- lyrics_transcriber/output/video.py +544 -0
- lyrics_transcriber/review/__init__.py +0 -0
- lyrics_transcriber/review/server.py +676 -0
- lyrics_transcriber/storage/__init__.py +0 -0
- lyrics_transcriber/storage/dropbox.py +225 -0
- lyrics_transcriber/transcribers/__init__.py +0 -0
- lyrics_transcriber/transcribers/audioshake.py +290 -0
- lyrics_transcriber/transcribers/base_transcriber.py +157 -0
- lyrics_transcriber/transcribers/whisper.py +330 -0
- lyrics_transcriber/types.py +648 -0
- lyrics_transcriber/utils/__init__.py +0 -0
- lyrics_transcriber/utils/word_utils.py +27 -0
- karaoke_gen-0.57.0.dist-info/METADATA +0 -167
- karaoke_gen-0.57.0.dist-info/RECORD +0 -23
- {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.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,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
|
+
|