lattifai 0.4.1__py3-none-any.whl → 0.4.3__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.
- lattifai/bin/agent.py +3 -8
- lattifai/bin/align.py +5 -7
- lattifai/bin/cli_base.py +3 -2
- lattifai/client.py +0 -3
- lattifai/io/reader.py +1 -1
- lattifai/tokenizer/tokenizer.py +1 -1
- lattifai/workflows/__init__.py +34 -0
- lattifai/workflows/agents.py +10 -0
- lattifai/workflows/base.py +192 -0
- lattifai/workflows/file_manager.py +812 -0
- lattifai/workflows/gemini.py +167 -0
- lattifai/workflows/prompts/README.md +22 -0
- lattifai/workflows/prompts/__init__.py +50 -0
- lattifai/workflows/prompts/gemini/README.md +24 -0
- lattifai/workflows/prompts/gemini/transcription_gem.txt +81 -0
- lattifai/workflows/youtube.py +931 -0
- {lattifai-0.4.1.dist-info → lattifai-0.4.3.dist-info}/METADATA +3 -4
- lattifai-0.4.3.dist-info/RECORD +39 -0
- {lattifai-0.4.1.dist-info → lattifai-0.4.3.dist-info}/top_level.txt +0 -1
- lattifai-0.4.1.dist-info/RECORD +0 -29
- /lattifai/io/{parser.py → text_parser.py} +0 -0
- {lattifai-0.4.1.dist-info → lattifai-0.4.3.dist-info}/WHEEL +0 -0
- {lattifai-0.4.1.dist-info → lattifai-0.4.3.dist-info}/entry_points.txt +0 -0
- {lattifai-0.4.1.dist-info → lattifai-0.4.3.dist-info}/licenses/LICENSE +0 -0
lattifai/bin/agent.py
CHANGED
|
@@ -18,12 +18,15 @@ from lattifai.io import OUTPUT_SUBTITLE_FORMATS
|
|
|
18
18
|
@cli.command()
|
|
19
19
|
@click.option('--youtube', '--yt', is_flag=True, help='Process YouTube URL through agentic workflow.')
|
|
20
20
|
@click.option(
|
|
21
|
+
'-K',
|
|
22
|
+
'-L',
|
|
21
23
|
'--api-key',
|
|
22
24
|
'--api_key',
|
|
23
25
|
type=str,
|
|
24
26
|
help='LattifAI API key for alignment (overrides LATTIFAI_API_KEY env var).',
|
|
25
27
|
)
|
|
26
28
|
@click.option(
|
|
29
|
+
'-G',
|
|
27
30
|
'--gemini-api-key',
|
|
28
31
|
'--gemini_api_key',
|
|
29
32
|
type=str,
|
|
@@ -135,14 +138,6 @@ def agent(
|
|
|
135
138
|
lattifai_api_key = api_key or os.getenv('LATTIFAI_API_KEY')
|
|
136
139
|
gemini_key = gemini_api_key or os.getenv('GEMINI_API_KEY')
|
|
137
140
|
|
|
138
|
-
if not gemini_key:
|
|
139
|
-
click.echo(
|
|
140
|
-
colorful.red(
|
|
141
|
-
'❌ Gemini API key is required. Set GEMINI_API_KEY environment variable or use --gemini-api-key option.'
|
|
142
|
-
)
|
|
143
|
-
)
|
|
144
|
-
return
|
|
145
|
-
|
|
146
141
|
try:
|
|
147
142
|
# Run the YouTube workflow
|
|
148
143
|
asyncio.run(
|
lattifai/bin/align.py
CHANGED
|
@@ -52,6 +52,8 @@ from lattifai.io import INPUT_SUBTITLE_FORMATS, OUTPUT_SUBTITLE_FORMATS
|
|
|
52
52
|
help='Model name or path for alignment.',
|
|
53
53
|
)
|
|
54
54
|
@click.option(
|
|
55
|
+
'-K',
|
|
56
|
+
'-L',
|
|
55
57
|
'--api-key',
|
|
56
58
|
'--api_key',
|
|
57
59
|
type=str,
|
|
@@ -178,6 +180,8 @@ def align(
|
|
|
178
180
|
help='Model name or path for alignment.',
|
|
179
181
|
)
|
|
180
182
|
@click.option(
|
|
183
|
+
'-K',
|
|
184
|
+
'-L',
|
|
181
185
|
'--api-key',
|
|
182
186
|
'--api_key',
|
|
183
187
|
type=str,
|
|
@@ -185,6 +189,7 @@ def align(
|
|
|
185
189
|
help='API key for LattifAI.',
|
|
186
190
|
)
|
|
187
191
|
@click.option(
|
|
192
|
+
'-G',
|
|
188
193
|
'--gemini-api-key',
|
|
189
194
|
'--gemini_api_key',
|
|
190
195
|
type=str,
|
|
@@ -223,13 +228,6 @@ def youtube(
|
|
|
223
228
|
|
|
224
229
|
# Get Gemini API key
|
|
225
230
|
gemini_key = gemini_api_key or os.getenv('GEMINI_API_KEY')
|
|
226
|
-
if not gemini_key:
|
|
227
|
-
click.echo(
|
|
228
|
-
colorful.red(
|
|
229
|
-
'❌ Gemini API key is required. Set GEMINI_API_KEY environment variable or use --gemini-api-key option.'
|
|
230
|
-
)
|
|
231
|
-
)
|
|
232
|
-
raise click.ClickException('Missing Gemini API key')
|
|
233
231
|
|
|
234
232
|
async def _process():
|
|
235
233
|
# Initialize components with their configuration (only config, not runtime params)
|
lattifai/bin/cli_base.py
CHANGED
|
@@ -9,9 +9,10 @@ def cli():
|
|
|
9
9
|
The shell entry point to Lattifai, a tool for audio data manipulation.
|
|
10
10
|
"""
|
|
11
11
|
# Load environment variables from .env file
|
|
12
|
-
from dotenv import load_dotenv
|
|
12
|
+
from dotenv import find_dotenv, load_dotenv
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
# Try to find and load .env file from current directory or parent directories
|
|
15
|
+
load_dotenv(find_dotenv(usecwd=True))
|
|
15
16
|
|
|
16
17
|
logging.basicConfig(
|
|
17
18
|
format='%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s',
|
lattifai/client.py
CHANGED
|
@@ -5,7 +5,6 @@ import os
|
|
|
5
5
|
from typing import Dict, List, Optional, Tuple, Union
|
|
6
6
|
|
|
7
7
|
import colorful
|
|
8
|
-
from dotenv import load_dotenv
|
|
9
8
|
from lhotse.utils import Pathlike
|
|
10
9
|
|
|
11
10
|
from lattifai.base_client import AsyncAPIClient, SyncAPIClient
|
|
@@ -22,8 +21,6 @@ from lattifai.io import SubtitleFormat, SubtitleIO, Supervision
|
|
|
22
21
|
from lattifai.tokenizer import AsyncLatticeTokenizer
|
|
23
22
|
from lattifai.utils import _load_tokenizer, _load_worker, _resolve_model_path, _select_device
|
|
24
23
|
|
|
25
|
-
load_dotenv()
|
|
26
|
-
|
|
27
24
|
|
|
28
25
|
class LattifAI(SyncAPIClient):
|
|
29
26
|
"""Synchronous LattifAI client."""
|
lattifai/io/reader.py
CHANGED
|
@@ -4,8 +4,8 @@ from typing import List, Literal, Optional, Union
|
|
|
4
4
|
|
|
5
5
|
from lhotse.utils import Pathlike
|
|
6
6
|
|
|
7
|
-
from .parser import parse_speaker_text
|
|
8
7
|
from .supervision import Supervision
|
|
8
|
+
from .text_parser import parse_speaker_text
|
|
9
9
|
|
|
10
10
|
SubtitleFormat = Literal['txt', 'srt', 'vtt', 'ass', 'auto']
|
|
11
11
|
|
lattifai/tokenizer/tokenizer.py
CHANGED
|
@@ -231,7 +231,7 @@ class LatticeTokenizer:
|
|
|
231
231
|
remainder = ''
|
|
232
232
|
# Detect and split special sentence types: e.g., '[APPLAUSE] >> MIRA MURATI:' -> ['[APPLAUSE]', '>> MIRA MURATI:'] # noqa: E501
|
|
233
233
|
resplit_parts = self._resplit_special_sentence_types(_sentence)
|
|
234
|
-
if any(resplit_parts[-1].endswith(sp) for sp in [':', ':'
|
|
234
|
+
if any(resplit_parts[-1].endswith(sp) for sp in [':', ':']):
|
|
235
235
|
if s < len(_sentences) - 1:
|
|
236
236
|
_sentences[s + 1] = resplit_parts[-1] + ' ' + _sentences[s + 1]
|
|
237
237
|
else: # last part
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LattifAI Agentic Workflows
|
|
3
|
+
|
|
4
|
+
This module provides agentic workflow capabilities for automated processing
|
|
5
|
+
of multimedia content through intelligent agent-based pipelines.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Import transcript processing functionality
|
|
9
|
+
from lattifai.io import (
|
|
10
|
+
ALL_SUBTITLE_FORMATS,
|
|
11
|
+
INPUT_SUBTITLE_FORMATS,
|
|
12
|
+
OUTPUT_SUBTITLE_FORMATS,
|
|
13
|
+
SUBTITLE_FORMATS,
|
|
14
|
+
GeminiReader,
|
|
15
|
+
GeminiWriter,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .agents import YouTubeSubtitleAgent
|
|
19
|
+
from .base import WorkflowAgent, WorkflowResult, WorkflowStep
|
|
20
|
+
from .file_manager import FileExistenceManager
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
'WorkflowAgent',
|
|
24
|
+
'WorkflowStep',
|
|
25
|
+
'WorkflowResult',
|
|
26
|
+
'YouTubeSubtitleAgent',
|
|
27
|
+
'FileExistenceManager',
|
|
28
|
+
'GeminiReader',
|
|
29
|
+
'GeminiWriter',
|
|
30
|
+
'SUBTITLE_FORMATS',
|
|
31
|
+
'INPUT_SUBTITLE_FORMATS',
|
|
32
|
+
'OUTPUT_SUBTITLE_FORMATS',
|
|
33
|
+
'ALL_SUBTITLE_FORMATS',
|
|
34
|
+
]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes for agentic workflows
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import abc
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Dict, List, Optional, Union
|
|
11
|
+
|
|
12
|
+
import colorful
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def setup_workflow_logger(name: str) -> logging.Logger:
|
|
16
|
+
"""Setup a logger with consistent formatting for workflow modules"""
|
|
17
|
+
logger = logging.getLogger(f'workflows.{name}')
|
|
18
|
+
|
|
19
|
+
# Only add handler if it doesn't exist
|
|
20
|
+
if not logger.handlers:
|
|
21
|
+
handler = logging.StreamHandler()
|
|
22
|
+
formatter = logging.Formatter(
|
|
23
|
+
'%(asctime)s - %(name)+17s.py:%(lineno)-4d - %(levelname)-8s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S'
|
|
24
|
+
)
|
|
25
|
+
handler.setFormatter(formatter)
|
|
26
|
+
logger.addHandler(handler)
|
|
27
|
+
logger.setLevel(logging.INFO)
|
|
28
|
+
logger.propagate = False
|
|
29
|
+
|
|
30
|
+
return logger
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
logger = setup_workflow_logger('base')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class WorkflowStatus(Enum):
|
|
37
|
+
"""Workflow execution status"""
|
|
38
|
+
|
|
39
|
+
PENDING = 'pending'
|
|
40
|
+
RUNNING = 'running'
|
|
41
|
+
COMPLETED = 'completed'
|
|
42
|
+
FAILED = 'failed'
|
|
43
|
+
RETRYING = 'retrying'
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class WorkflowResult:
|
|
48
|
+
"""Result of a workflow execution"""
|
|
49
|
+
|
|
50
|
+
status: WorkflowStatus
|
|
51
|
+
data: Optional[Any] = None
|
|
52
|
+
error: Optional[str] = None
|
|
53
|
+
exception: Optional[Exception] = None # Store the original exception object
|
|
54
|
+
execution_time: Optional[float] = None
|
|
55
|
+
step_results: Optional[List[Dict[str, Any]]] = None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def is_success(self) -> bool:
|
|
59
|
+
return self.status == WorkflowStatus.COMPLETED
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def is_error(self) -> bool:
|
|
63
|
+
return self.status == WorkflowStatus.FAILED
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class WorkflowStep:
|
|
68
|
+
"""Individual step in a workflow"""
|
|
69
|
+
|
|
70
|
+
name: str
|
|
71
|
+
description: str
|
|
72
|
+
required: bool = True
|
|
73
|
+
retry_count: int = 0
|
|
74
|
+
max_retries: int = 1
|
|
75
|
+
|
|
76
|
+
def should_retry(self) -> bool:
|
|
77
|
+
return self.retry_count < self.max_retries
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class WorkflowAgent(abc.ABC):
|
|
81
|
+
"""Base class for agentic workflows"""
|
|
82
|
+
|
|
83
|
+
def __init__(self, name: str, max_retries: int = 0):
|
|
84
|
+
self.name = name
|
|
85
|
+
self.max_retries = max_retries
|
|
86
|
+
self.steps: List[WorkflowStep] = []
|
|
87
|
+
self.logger = setup_workflow_logger('agent')
|
|
88
|
+
|
|
89
|
+
@abc.abstractmethod
|
|
90
|
+
def define_steps(self) -> List[WorkflowStep]:
|
|
91
|
+
"""Define the workflow steps"""
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
@abc.abstractmethod
|
|
95
|
+
async def execute_step(self, step: WorkflowStep, context: Dict[str, Any]) -> Any:
|
|
96
|
+
"""Execute a single workflow step"""
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
def setup(self):
|
|
100
|
+
"""Setup the workflow"""
|
|
101
|
+
self.steps = self.define_steps()
|
|
102
|
+
for step in self.steps:
|
|
103
|
+
step.max_retries = self.max_retries
|
|
104
|
+
|
|
105
|
+
async def execute(self, **kwargs) -> WorkflowResult:
|
|
106
|
+
"""Execute the complete workflow"""
|
|
107
|
+
if not self.steps:
|
|
108
|
+
self.setup()
|
|
109
|
+
|
|
110
|
+
start_time = time.time()
|
|
111
|
+
context = kwargs.copy()
|
|
112
|
+
step_results = []
|
|
113
|
+
|
|
114
|
+
self.logger.info(colorful.bold_white_on_green(f'🚀 Starting workflow: {self.name}'))
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
for i, step in enumerate(self.steps):
|
|
118
|
+
step_info = f'📋 Step {i + 1}/{len(self.steps)}: {step.name}'
|
|
119
|
+
self.logger.info(colorful.bold_white_on_green(step_info))
|
|
120
|
+
|
|
121
|
+
step_start = time.time()
|
|
122
|
+
step_result = await self._execute_step_with_retry(step, context)
|
|
123
|
+
step_duration = time.time() - step_start
|
|
124
|
+
|
|
125
|
+
step_results.append(
|
|
126
|
+
{'step_name': step.name, 'status': 'completed', 'duration': step_duration, 'result': step_result}
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Update context with step result
|
|
130
|
+
context[f'step_{i}_result'] = step_result
|
|
131
|
+
context[f'{step.name.lower().replace(" ", "_")}_result'] = step_result
|
|
132
|
+
|
|
133
|
+
self.logger.info(f'✅ Step {i + 1} completed in {step_duration:.2f}s')
|
|
134
|
+
|
|
135
|
+
execution_time = time.time() - start_time
|
|
136
|
+
self.logger.info(f'🎉 Workflow completed in {execution_time:.2f}s')
|
|
137
|
+
|
|
138
|
+
return WorkflowResult(
|
|
139
|
+
status=WorkflowStatus.COMPLETED, data=context, execution_time=execution_time, step_results=step_results
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
execution_time = time.time() - start_time
|
|
144
|
+
# For LattifAI errors, just log the error code and basic message
|
|
145
|
+
from lattifai.errors import LattifAIError
|
|
146
|
+
|
|
147
|
+
if isinstance(e, LattifAIError):
|
|
148
|
+
self.logger.error(f'❌ Workflow failed after {execution_time:.2f}s: [{e.error_code}] {e.message}')
|
|
149
|
+
else:
|
|
150
|
+
self.logger.error(f'❌ Workflow failed after {execution_time:.2f}s: {str(e)}')
|
|
151
|
+
|
|
152
|
+
return WorkflowResult(
|
|
153
|
+
status=WorkflowStatus.FAILED,
|
|
154
|
+
error=str(e),
|
|
155
|
+
exception=e, # Store the original exception
|
|
156
|
+
execution_time=execution_time,
|
|
157
|
+
step_results=step_results,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
async def _execute_step_with_retry(self, step: WorkflowStep, context: Dict[str, Any]) -> Any:
|
|
161
|
+
"""Execute a step with retry logic"""
|
|
162
|
+
last_error = None
|
|
163
|
+
|
|
164
|
+
for attempt in range(step.max_retries + 1):
|
|
165
|
+
try:
|
|
166
|
+
if attempt > 0:
|
|
167
|
+
self.logger.info(f'🔄 Retrying step {step.name} (attempt {attempt + 1}/{step.max_retries + 1})')
|
|
168
|
+
|
|
169
|
+
result = await self.execute_step(step, context)
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
last_error = e
|
|
174
|
+
step.retry_count += 1
|
|
175
|
+
|
|
176
|
+
# For LattifAI errors, show simplified message in logs
|
|
177
|
+
from lattifai.errors import LattifAIError
|
|
178
|
+
|
|
179
|
+
error_summary = f'[{e.error_code}]' if isinstance(e, LattifAIError) else str(e)[:100]
|
|
180
|
+
|
|
181
|
+
if step.should_retry():
|
|
182
|
+
self.logger.warning(f'⚠️ Step {step.name} failed: {error_summary}. Retrying...')
|
|
183
|
+
continue
|
|
184
|
+
else:
|
|
185
|
+
self.logger.error(
|
|
186
|
+
f'❌ Step {step.name} failed after {step.max_retries + 1} attempts: {error_summary}'
|
|
187
|
+
)
|
|
188
|
+
raise e
|
|
189
|
+
|
|
190
|
+
# This should never be reached, but just in case
|
|
191
|
+
if last_error:
|
|
192
|
+
raise last_error
|