Spaces:
Sleeping
Sleeping
miaohaiyuan
commited on
Commit
•
36a4bf9
1
Parent(s):
c0e9d94
init deliver
Browse files- .gitignore +5 -0
- app.py +454 -0
- download.py +91 -0
- requirements.txt +11 -0
- translation_agent/__init__.py +1 -0
- translation_agent/utils.py +689 -0
.gitignore
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.env
|
2 |
+
venv
|
3 |
+
.venv
|
4 |
+
**.pyc
|
5 |
+
downloads/
|
app.py
ADDED
@@ -0,0 +1,454 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import json
|
3 |
+
import os
|
4 |
+
import sys
|
5 |
+
import tempfile
|
6 |
+
import whisperx
|
7 |
+
import ffmpeg
|
8 |
+
import tiktoken
|
9 |
+
from io import BytesIO
|
10 |
+
from glob import glob
|
11 |
+
from dotenv import load_dotenv
|
12 |
+
from download import download_video_audio, delete_download
|
13 |
+
from groq import Groq
|
14 |
+
from openai import OpenAI
|
15 |
+
from langdetect import detect
|
16 |
+
from langdetect.lang_detect_exception import LangDetectException
|
17 |
+
from translation_agent.utils import *
|
18 |
+
|
19 |
+
|
20 |
+
os.environ["FFMPEG_PATH"] = "D:\\ffmpeg\\bin\\ffmpeg.exe"
|
21 |
+
os.environ["no_proxy"] = "localhost, 127.0.0.1, ::1"
|
22 |
+
|
23 |
+
load_dotenv()
|
24 |
+
|
25 |
+
GROQ_API_KEY = os.environ.get("GROQ_API_KEY", None)
|
26 |
+
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"))
|
27 |
+
model = os.getenv("OPENAI_MODEL") or "gpt-4o"
|
28 |
+
|
29 |
+
MAX_FILE_SIZE = 100 * 1024 * 1024 # 100 MB
|
30 |
+
FILE_TOO_LARGE_MESSAGE = "The audio file is too large. If you used a YouTube link, please try a shorter video clip. If you uploaded an audio file, try trimming or compressing the audio to under 100 MB."
|
31 |
+
|
32 |
+
audio_file_path = None
|
33 |
+
progress=gr.Progress()
|
34 |
+
|
35 |
+
|
36 |
+
def detect_language(text):
|
37 |
+
try:
|
38 |
+
language = detect(text)
|
39 |
+
return language
|
40 |
+
except LangDetectException as e:
|
41 |
+
print(f"Error detecting language: {e}")
|
42 |
+
return None
|
43 |
+
|
44 |
+
class GenerationStatistics:
|
45 |
+
def __init__(self, input_time=0, output_time=0, input_tokens=0, output_tokens=0, total_time=0, model_name=model):
|
46 |
+
self.input_time = input_time
|
47 |
+
self.output_time = output_time
|
48 |
+
self.input_tokens = input_tokens
|
49 |
+
self.output_tokens = output_tokens
|
50 |
+
self.total_time = total_time
|
51 |
+
self.model_name = model_name
|
52 |
+
|
53 |
+
def get_input_speed(self):
|
54 |
+
return self.input_tokens / self.input_time if self.input_time != 0 else 0
|
55 |
+
|
56 |
+
def get_output_speed(self):
|
57 |
+
return self.output_tokens / self.output_time if self.output_time != 0 else 0
|
58 |
+
|
59 |
+
def add(self, other):
|
60 |
+
if not isinstance(other, GenerationStatistics):
|
61 |
+
raise TypeError("Can only add GenerationStatistics objects")
|
62 |
+
|
63 |
+
self.input_time += other.input_time
|
64 |
+
self.output_time += other.output_time
|
65 |
+
self.input_tokens += other.input_tokens
|
66 |
+
self.output_tokens += other.output_tokens
|
67 |
+
self.total_time += other.total_time
|
68 |
+
|
69 |
+
def __str__(self):
|
70 |
+
return (f"\n## {self.get_output_speed():.2f} T/s ⚡\nRound trip time: {self.total_time:.2f}s Model: {self.model_name}\n\n"
|
71 |
+
f"| Metric | Input | Output | Total |\n"
|
72 |
+
f"|-----------------|----------------|-----------------|----------------|\n"
|
73 |
+
f"| Speed (T/s) | {self.get_input_speed():.2f} | {self.get_output_speed():.2f} | {(self.input_tokens + self.output_tokens) / self.total_time if self.total_time != 0 else 0:.2f} |\n"
|
74 |
+
f"| Tokens | {self.input_tokens} | {self.output_tokens} | {self.input_tokens + self.output_tokens} |\n"
|
75 |
+
f"| Inference Time (s) | {self.input_time:.2f} | {self.output_time:.2f} | {self.total_time:.2f} |")
|
76 |
+
|
77 |
+
class NoteSection:
|
78 |
+
def __init__(self, structure, transcript):
|
79 |
+
self.structure = structure
|
80 |
+
self.contents = {title: "" for title in self.flatten_structure(structure)}
|
81 |
+
|
82 |
+
def flatten_structure(self, structure):
|
83 |
+
sections = []
|
84 |
+
for title, content in structure.items():
|
85 |
+
sections.append(title)
|
86 |
+
if isinstance(content, dict):
|
87 |
+
sections.extend(self.flatten_structure(content))
|
88 |
+
return sections
|
89 |
+
|
90 |
+
def update_content(self, title, new_content):
|
91 |
+
try:
|
92 |
+
self.contents[title] += new_content
|
93 |
+
except TypeError as e:
|
94 |
+
pass
|
95 |
+
|
96 |
+
def return_existing_contents(self, level=1) -> str:
|
97 |
+
existing_content = ""
|
98 |
+
for title, content in self.structure.items():
|
99 |
+
if self.contents[title].strip():
|
100 |
+
existing_content += f"{'#' * level} {title}\n{self.contents[title]}.\n\n"
|
101 |
+
if isinstance(content, dict):
|
102 |
+
existing_content += self.get_markdown_content(content, level + 1)
|
103 |
+
return existing_content
|
104 |
+
|
105 |
+
def get_markdown_content(self, structure=None, level=1):
|
106 |
+
if structure is None:
|
107 |
+
structure = self.structure
|
108 |
+
|
109 |
+
markdown_content = ""
|
110 |
+
for title, content in structure.items():
|
111 |
+
if self.contents[title].strip():
|
112 |
+
markdown_content += f"{'#' * level} {title}\n{self.contents[title]}.\n\n"
|
113 |
+
if isinstance(content, dict):
|
114 |
+
markdown_content += self.get_markdown_content(content, level + 1)
|
115 |
+
return markdown_content
|
116 |
+
|
117 |
+
def transcribe_audio(audio_file):
|
118 |
+
model = whisperx.load_model("small", device="cuda", compute_type="float16")
|
119 |
+
result = model.transcribe(audio_file)
|
120 |
+
|
121 |
+
transcription = ''
|
122 |
+
segments = result['segments']
|
123 |
+
for segment in segments:
|
124 |
+
transcription += segment['text']
|
125 |
+
transcription += '\n'
|
126 |
+
|
127 |
+
return transcription
|
128 |
+
|
129 |
+
def generate_notes_structure(transcript: str, model: str = model, lang: str="en"):
|
130 |
+
|
131 |
+
shot_example = """
|
132 |
+
"Introduction": "Introduction to the AMA session, including the topic of Groq scaling architecture and the panelists",
|
133 |
+
"Panelist Introductions": "Brief introductions from Igor, Andrew, and Omar, covering their backgrounds and roles at Groq",
|
134 |
+
"Groq Scaling Architecture Overview": "High-level overview of Groq's scaling architecture, covering hardware, software, and cloud components",
|
135 |
+
"Hardware Perspective": "Igor's overview of Groq's hardware approach, using an analogy of city traffic management to explain the traditional compute approach and Groq's innovative approach",
|
136 |
+
"Traditional Compute": "Description of traditional compute approach, including asynchronous nature, queues, and poor utilization of infrastructure",
|
137 |
+
"Groq's Approach": "Description of Groq's approach, including pre-orchestrated movement of data, low latency, high energy efficiency, and high utilization of resources",
|
138 |
+
"Hardware Implementation": "Igor's explanation of the hardware implementation, including a comparison of GPU and LPU architectures"
|
139 |
+
}"""
|
140 |
+
|
141 |
+
messages=[
|
142 |
+
{
|
143 |
+
"role": "system",
|
144 |
+
"content": "Write in JSON format:\n\n{\"Title of section goes here\":\"Description of section goes here\",\"Title of section goes here\":\"Description of section goes here\",\"Title of section goes here\":\"Description of section goes here\"}"
|
145 |
+
},
|
146 |
+
{
|
147 |
+
"role": "user",
|
148 |
+
#"content": f"Please respond in language [{lang}]. ### Transcript {transcript}\n\n### Example\n\n{shot_example}### Instructions\n\nCreate a structure for comprehensive notes on the above Transcript information. The output format is shown in examples. The section titles and content descriptions must be comprehensive and adapt to the content of transcript. Quality over quantity. "
|
149 |
+
"content": f"Please respond in language [{lang}]. ### Transcript {transcript}\n\n### Instructions\n\nCreate a structure for comprehensive notes on the above Transcript information. The output is json format as shown in system content. Section titles and content descriptions must be comprehensive and adapt to the content of transcript. Quality over quantity. "
|
150 |
+
}
|
151 |
+
]
|
152 |
+
|
153 |
+
completion = client.chat.completions.create(
|
154 |
+
model=model,
|
155 |
+
messages=messages,
|
156 |
+
temperature=0.3,
|
157 |
+
#max_tokens=128000,
|
158 |
+
top_p=1,
|
159 |
+
stream=False,
|
160 |
+
stop=None,
|
161 |
+
)
|
162 |
+
|
163 |
+
usage = completion.usage
|
164 |
+
#statistics_to_return = GenerationStatistics(input_tokens=usage.prompt_tokens, output_tokens=usage.completion_tokens, model_name=model)
|
165 |
+
return completion.choices[0].message.content
|
166 |
+
|
167 |
+
def generate_section(transcript: str, existing_notes: str, section: str, model: str = model, lang: str="en"):
|
168 |
+
|
169 |
+
stream = client.chat.completions.create(
|
170 |
+
model=model,
|
171 |
+
messages=[
|
172 |
+
{
|
173 |
+
"role": "system",
|
174 |
+
"content": "Respond in language [{lang}]. You are an expert writer. Generate a comprehensive note for the section provided based factually on the transcript provided. Do *not* repeat any content from previous sections."
|
175 |
+
},
|
176 |
+
{
|
177 |
+
"role": "user",
|
178 |
+
"content": f"Respond in language [{lang}]. ### Transcript\n\n{transcript}\n\n### Existing Notes\n\n{existing_notes}\n\n### Instructions\n\nGenerate comprehensive notes for this section only based on the transcript: \n\n{section}."
|
179 |
+
}
|
180 |
+
],
|
181 |
+
temperature=0.3,
|
182 |
+
max_tokens=16000,
|
183 |
+
top_p=1,
|
184 |
+
stream=True,
|
185 |
+
stop=None,
|
186 |
+
)
|
187 |
+
|
188 |
+
for chunk in stream:
|
189 |
+
tokens = chunk.choices[0].delta.content
|
190 |
+
if tokens:
|
191 |
+
yield tokens
|
192 |
+
|
193 |
+
def process_audio(audio_file, youtube_link):
|
194 |
+
global audio_file_path
|
195 |
+
|
196 |
+
i = 1; j = 3 #for progress
|
197 |
+
if youtube_link:
|
198 |
+
j += 1; progress((i,j), desc="download video..."); i += 1
|
199 |
+
audio_file_path = download_video_audio(youtube_link)
|
200 |
+
if audio_file_path is None:
|
201 |
+
return "Failed to download audio from YouTube link. Please try again."
|
202 |
+
elif audio_file:
|
203 |
+
audio_file_path = audio_file.name
|
204 |
+
else:
|
205 |
+
return "Please provide either an audio file or a YouTube link."
|
206 |
+
|
207 |
+
if os.path.getsize(audio_file_path) > MAX_FILE_SIZE:
|
208 |
+
return FILE_TOO_LARGE_MESSAGE
|
209 |
+
|
210 |
+
progress((i,j), desc="Start transcribe audio..."); i += 1
|
211 |
+
transcription_text = transcribe_audio(audio_file_path)
|
212 |
+
print("transcription_text=",transcription_text)
|
213 |
+
encoding = tiktoken.get_encoding("cl100k_base")
|
214 |
+
token_count = len(encoding.encode(transcription_text))
|
215 |
+
print("token_count=",token_count)
|
216 |
+
#transcription_output.label = f"tokens: {token_count}"
|
217 |
+
lang = detect_language(transcription_text[:100])
|
218 |
+
print("detect lang=",lang)
|
219 |
+
|
220 |
+
progress((i,j), desc="Generating notes structure..."); i += 1
|
221 |
+
notes_structure = generate_notes_structure(transcription_text, lang=lang)
|
222 |
+
print("notes_structure=", notes_structure)
|
223 |
+
|
224 |
+
progress((i,j), desc="Generating notes section..."); i += 1
|
225 |
+
try:
|
226 |
+
notes_structure_json = json.loads(notes_structure)
|
227 |
+
print("notes_structure_json=",notes_structure_json)
|
228 |
+
notes = NoteSection(structure=notes_structure_json, transcript=transcription_text)
|
229 |
+
|
230 |
+
total_generation_statistics = GenerationStatistics(model_name=model)
|
231 |
+
|
232 |
+
for title, content in notes_structure_json.items():
|
233 |
+
if isinstance(content, str):
|
234 |
+
content_stream = generate_section(transcript=transcription_text, existing_notes=notes.return_existing_contents(), section=(title + ": " + content), lang=lang)
|
235 |
+
for chunk in content_stream:
|
236 |
+
if isinstance(chunk, GenerationStatistics):
|
237 |
+
total_generation_statistics.add(chunk)
|
238 |
+
elif chunk is not None:
|
239 |
+
notes.update_content(title, chunk)
|
240 |
+
|
241 |
+
return transcription_text, notes.get_markdown_content(), notes.get_markdown_content()
|
242 |
+
except json.JSONDecodeError:
|
243 |
+
return "Failed to decode the notes structure. Please try again."
|
244 |
+
|
245 |
+
def generate_notes(transcription_text):
|
246 |
+
lang = detect_language(transcription_text[:100])
|
247 |
+
print("detect lang=",lang)
|
248 |
+
notes_structure = generate_notes_structure(transcription_text, lang=lang)
|
249 |
+
print("notes_structure=", notes_structure)
|
250 |
+
|
251 |
+
try:
|
252 |
+
notes_structure_json = json.loads(notes_structure)
|
253 |
+
print("notes_structure_json=",notes_structure_json)
|
254 |
+
notes = NoteSection(structure=notes_structure_json, transcript=transcription_text)
|
255 |
+
|
256 |
+
total_generation_statistics = GenerationStatistics(model_name=model)
|
257 |
+
|
258 |
+
for title, content in notes_structure_json.items():
|
259 |
+
if isinstance(content, str):
|
260 |
+
content_stream = generate_section(transcript=transcription_text, existing_notes=notes.return_existing_contents(), section=(title + ": " + content), lang=lang)
|
261 |
+
for chunk in content_stream:
|
262 |
+
if isinstance(chunk, GenerationStatistics):
|
263 |
+
total_generation_statistics.add(chunk)
|
264 |
+
elif chunk is not None:
|
265 |
+
notes.update_content(title, chunk)
|
266 |
+
|
267 |
+
return notes.get_markdown_content(), notes.get_markdown_content()
|
268 |
+
except json.JSONDecodeError:
|
269 |
+
return "Failed to decode the notes structure. Please try again."
|
270 |
+
'''
|
271 |
+
iface = gr.Interface(
|
272 |
+
fn=process_audio,
|
273 |
+
inputs=[
|
274 |
+
gr.File(label="Upload Audio File"),
|
275 |
+
gr.Textbox(label="YouTube Link"),
|
276 |
+
gr.Textbox(label="Groq API Key", type="password")
|
277 |
+
],
|
278 |
+
outputs=gr.Textbox(label="Generated Notes"),
|
279 |
+
title="GroqNotes: Create structured notes from audio",
|
280 |
+
description="Generate notes from audio using Groq, Whisper, and Llama3"
|
281 |
+
)
|
282 |
+
'''
|
283 |
+
CSS = """
|
284 |
+
h1 {
|
285 |
+
text-align: center;
|
286 |
+
display: block;
|
287 |
+
height: 10vh;
|
288 |
+
align-content: center;
|
289 |
+
}
|
290 |
+
footer {
|
291 |
+
visibility: hidden;
|
292 |
+
}
|
293 |
+
.texts {
|
294 |
+
min-height: 100px;
|
295 |
+
}
|
296 |
+
"""
|
297 |
+
|
298 |
+
def clear():
|
299 |
+
return None, None, None, None, None, None
|
300 |
+
|
301 |
+
|
302 |
+
def translate_text(source_text, source_lang, target_lang, country=None, max_tokens=MAX_TOKENS_PER_CHUNK):
|
303 |
+
ic(f"start to translate transcription from {source_lang} to {target_lang}")
|
304 |
+
|
305 |
+
num_tokens_in_text = num_tokens_in_string(source_text)
|
306 |
+
ic(num_tokens_in_text)
|
307 |
+
|
308 |
+
if num_tokens_in_text < max_tokens:
|
309 |
+
ic("Translating text as single chunk")
|
310 |
+
|
311 |
+
progress((1,3), desc="First translation...")
|
312 |
+
#Note: use yield from B() if put yield in function B()
|
313 |
+
translation_1 = one_chunk_initial_translation(
|
314 |
+
source_lang, target_lang, source_text
|
315 |
+
)
|
316 |
+
yield translation_1, None
|
317 |
+
|
318 |
+
progress((2,3), desc="Reflecton...")
|
319 |
+
reflection = one_chunk_reflect_on_translation(
|
320 |
+
source_lang, target_lang, source_text, translation_1, country
|
321 |
+
)
|
322 |
+
yield reflection, None
|
323 |
+
|
324 |
+
progress((3,3), desc="Final translation...")
|
325 |
+
translation_2 = one_chunk_improve_translation(
|
326 |
+
source_lang, target_lang, source_text, translation_1, reflection
|
327 |
+
)
|
328 |
+
|
329 |
+
yield translation_2, None
|
330 |
+
|
331 |
+
else:
|
332 |
+
ic("Translating text as multiple chunks")
|
333 |
+
|
334 |
+
token_size = calculate_chunk_size(
|
335 |
+
token_count=num_tokens_in_text, token_limit=max_tokens
|
336 |
+
)
|
337 |
+
|
338 |
+
ic(token_size)
|
339 |
+
|
340 |
+
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
|
341 |
+
model_name = "gpt-4",
|
342 |
+
chunk_size=token_size,
|
343 |
+
chunk_overlap=0,
|
344 |
+
)
|
345 |
+
|
346 |
+
source_text_chunks = text_splitter.split_text(source_text)
|
347 |
+
progress((1,3), desc="First translation...")
|
348 |
+
translation_1_chunks = multichunk_initial_translation(
|
349 |
+
source_lang, target_lang, source_text_chunks
|
350 |
+
)
|
351 |
+
ic(translation_1_chunks)
|
352 |
+
translation_1 = "".join(translation_1_chunks)
|
353 |
+
#yield translation_1, None, None
|
354 |
+
|
355 |
+
progress((2,3), desc="Reflecton...")
|
356 |
+
reflection_chunks = multichunk_reflect_on_translation(
|
357 |
+
source_lang,
|
358 |
+
target_lang,
|
359 |
+
source_text_chunks,
|
360 |
+
translation_1_chunks,
|
361 |
+
country,
|
362 |
+
)
|
363 |
+
ic(reflection_chunks)
|
364 |
+
reflection = "".join(reflection_chunks)
|
365 |
+
#yield translation_1, reflection, None
|
366 |
+
progress((3,3), desc="Final translation...")
|
367 |
+
translation_2_chunks = multichunk_improve_translation(
|
368 |
+
source_lang,
|
369 |
+
target_lang,
|
370 |
+
source_text_chunks,
|
371 |
+
translation_1_chunks,
|
372 |
+
reflection_chunks,
|
373 |
+
)
|
374 |
+
ic(translation_2_chunks)
|
375 |
+
translation_2 = "".join(translation_2_chunks)
|
376 |
+
yield translation_2, None
|
377 |
+
|
378 |
+
def export_txt(strings):
|
379 |
+
if (strings is not None):
|
380 |
+
os.makedirs("outputs", exist_ok=True)
|
381 |
+
base_count = len(glob(os.path.join("outputs", "*.txt")))
|
382 |
+
file_path = os.path.join("outputs", f"{base_count:06d}.txt")
|
383 |
+
with open(file_path, "w", encoding="utf-8") as f:
|
384 |
+
f.write(strings)
|
385 |
+
return gr.update(value=file_path, label = "Ready to download markdown Summary")
|
386 |
+
|
387 |
+
def update_ui(transcription_output):
|
388 |
+
tokens = ""
|
389 |
+
encoding = tiktoken.get_encoding("cl100k_base")
|
390 |
+
token_count = len(encoding.encode(transcription_output))
|
391 |
+
print("token_count=",token_count)
|
392 |
+
if (token_count > 0):
|
393 |
+
tokens = f"tokens: {token_count}"
|
394 |
+
return gr.update(label=tokens)
|
395 |
+
|
396 |
+
with gr.Blocks(theme="soft", css=CSS) as demo:
|
397 |
+
gr.Markdown("# Whisper and Translation Agent")
|
398 |
+
|
399 |
+
with gr.Row():
|
400 |
+
with gr.Column(scale=1):
|
401 |
+
endpoint = gr.Dropdown(
|
402 |
+
label="Endpoint",
|
403 |
+
choices=["Groq","OpenAI","DeepSeek","Baichuan","Ollama","Huggingface"],
|
404 |
+
value="Groq",
|
405 |
+
)
|
406 |
+
model = gr.Textbox(label="Model", value="llama3-70b-8192", )
|
407 |
+
api_key = gr.Textbox(label="API_KEY", type="password", )
|
408 |
+
|
409 |
+
with gr.Column(scale=5):
|
410 |
+
with gr.Row():
|
411 |
+
file_input = gr.File(file_types=["audio", "video"])
|
412 |
+
text_input = gr.Textbox(placeholder="Enter youtube link")
|
413 |
+
|
414 |
+
with gr.Row():
|
415 |
+
clear_btn = gr.Button("CLEAR")
|
416 |
+
extract_btn = gr.Button("Extract")
|
417 |
+
|
418 |
+
with gr.Row():
|
419 |
+
with gr.Column(scale=1):
|
420 |
+
source_lang = gr.Dropdown(label="Source Lang(Auto-Detect)", choices=["English", "Chinese", "Spanish"], value="English")
|
421 |
+
target_lang = gr.Dropdown(label="Target Lang", choices=["English", "Chinese", "Spanish"], value="Chinese")
|
422 |
+
switch_Btn = gr.Button(value="🔄️")
|
423 |
+
translate_btn = gr.Button("Translate")
|
424 |
+
download_btn = gr.DownloadButton(label="Download")
|
425 |
+
|
426 |
+
|
427 |
+
with gr.Column(scale=5):
|
428 |
+
with gr.Row():
|
429 |
+
with gr.Tab("Transcription"):
|
430 |
+
transcription_output = gr.Textbox(label='', lines=5, show_copy_button=True)
|
431 |
+
|
432 |
+
with gr.Tab("Summary"):
|
433 |
+
summary_output = gr.Textbox(label='', lines=5, show_copy_button=True, elem_classes="texts")
|
434 |
+
|
435 |
+
with gr.Tab("Markdown"):
|
436 |
+
markdown_output = gr.Markdown(label='Markdown Summary', elem_classes="texts", height=500)
|
437 |
+
|
438 |
+
with gr.Row():
|
439 |
+
with gr.Tab("Translated Transcription"):
|
440 |
+
translated_transcription_output = gr.Textbox(label='', lines=5, show_copy_button=True)
|
441 |
+
with gr.Tab("Translated Summary"):
|
442 |
+
translated_summary_output = gr.Textbox(label='', elem_classes="texts", lines=5, show_copy_button=True)
|
443 |
+
with gr.Tab("Markdown Summary"):
|
444 |
+
translated_summary_markdown = gr.Markdown(label='', elem_classes="texts", height=500)
|
445 |
+
|
446 |
+
clear_btn.click(clear, outputs=[file_input, text_input, transcription_output, summary_output, translated_transcription_output, translated_summary_output])
|
447 |
+
extract_btn.click(process_audio, inputs=[file_input, text_input], outputs=[transcription_output, summary_output, markdown_output])
|
448 |
+
translate_btn.click(translate_text, inputs=[transcription_output, source_lang, target_lang], outputs=[translated_transcription_output]
|
449 |
+
).then(
|
450 |
+
generate_notes, inputs=[translated_transcription_output], outputs=[translated_summary_output,translated_summary_markdown])
|
451 |
+
translated_summary_markdown.change(fn=export_txt, inputs=translated_summary_markdown, outputs=[download_btn])
|
452 |
+
|
453 |
+
if __name__ == "__main__":
|
454 |
+
demo.launch()
|
download.py
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import unicode_literals
|
2 |
+
import yt_dlp as youtube_dl
|
3 |
+
import os
|
4 |
+
import time
|
5 |
+
import os
|
6 |
+
import shutil
|
7 |
+
|
8 |
+
max_retries = 3
|
9 |
+
delay = 2
|
10 |
+
|
11 |
+
|
12 |
+
class MyLogger(object):
|
13 |
+
def __init__(self, external_logger=lambda x: None):
|
14 |
+
self.external_logger = external_logger
|
15 |
+
|
16 |
+
def debug(self, msg):
|
17 |
+
print("[debug]: ", msg)
|
18 |
+
self.external_logger(msg)
|
19 |
+
|
20 |
+
def warning(self, msg):
|
21 |
+
print("[warning]: ", msg)
|
22 |
+
|
23 |
+
def error(self, msg):
|
24 |
+
print("[error]: ", msg)
|
25 |
+
|
26 |
+
|
27 |
+
def my_hook(d):
|
28 |
+
print("hook", d["status"])
|
29 |
+
if d["status"] == "finished":
|
30 |
+
print("Done downloading, now converting ...")
|
31 |
+
|
32 |
+
|
33 |
+
def get_ydl_opts(external_logger=lambda x: None):
|
34 |
+
return {
|
35 |
+
"format": "bestaudio/best",
|
36 |
+
"postprocessors": [
|
37 |
+
{
|
38 |
+
"key": "FFmpegExtractAudio",
|
39 |
+
"preferredcodec": "mp3",
|
40 |
+
"preferredquality": "192", # set the preferred bitrate to 192kbps
|
41 |
+
}
|
42 |
+
],
|
43 |
+
"logger": MyLogger(external_logger),
|
44 |
+
"outtmpl": "./downloads/audio/%(title)s.%(ext)s", # Set the output filename directly
|
45 |
+
"progress_hooks": [my_hook],
|
46 |
+
}
|
47 |
+
|
48 |
+
|
49 |
+
def download_video_audio(url, external_logger=lambda x: None):
|
50 |
+
retries = 0
|
51 |
+
while retries < max_retries:
|
52 |
+
try:
|
53 |
+
ydl_opts = get_ydl_opts(external_logger)
|
54 |
+
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
55 |
+
print("Going to download ", url)
|
56 |
+
info = ydl.extract_info(url, download=False)
|
57 |
+
filename = ydl.prepare_filename(info)
|
58 |
+
res = ydl.download([url])
|
59 |
+
print("youtube-dl result :", res)
|
60 |
+
mp3_filename = os.path.splitext(filename)[0] + '.mp3'
|
61 |
+
print('mp3 file name - ', mp3_filename)
|
62 |
+
return mp3_filename
|
63 |
+
except Exception as e:
|
64 |
+
retries += 1
|
65 |
+
print(
|
66 |
+
f"An error occurred during downloading (Attempt {retries}/{max_retries}):",
|
67 |
+
str(e),
|
68 |
+
)
|
69 |
+
if retries >= max_retries:
|
70 |
+
raise e
|
71 |
+
time.sleep(delay)
|
72 |
+
|
73 |
+
|
74 |
+
|
75 |
+
def delete_download(path):
|
76 |
+
try:
|
77 |
+
if os.path.isfile(path):
|
78 |
+
os.remove(path)
|
79 |
+
print(f"File {path} has been deleted.")
|
80 |
+
elif os.path.isdir(path):
|
81 |
+
shutil.rmtree(path)
|
82 |
+
print(f"Directory {path} and its contents have been deleted.")
|
83 |
+
else:
|
84 |
+
print(f"The path {path} is neither a file nor a directory.")
|
85 |
+
except PermissionError:
|
86 |
+
print(f"Permission denied: Unable to delete {path}.")
|
87 |
+
except FileNotFoundError:
|
88 |
+
print(f"File or directory not found: {path}")
|
89 |
+
except Exception as e:
|
90 |
+
print(f"An error occurred while trying to delete {path}: {str(e)}")
|
91 |
+
|
requirements.txt
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
ffmpeg_python==0.2.0
|
2 |
+
gradio==4.38.1
|
3 |
+
groq==0.9.0
|
4 |
+
icecream==2.1.3
|
5 |
+
langchain_text_splitters==0.2.2
|
6 |
+
langdetect==1.0.9
|
7 |
+
openai==1.35.14
|
8 |
+
python-dotenv==1.0.1
|
9 |
+
tiktoken==0.5.2
|
10 |
+
whisperx==3.1.1
|
11 |
+
yt_dlp==2024.7.16
|
translation_agent/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from .utils import translate
|
translation_agent/utils.py
ADDED
@@ -0,0 +1,689 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from typing import List
|
3 |
+
from typing import Union
|
4 |
+
|
5 |
+
import openai
|
6 |
+
import tiktoken
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
from icecream import ic
|
9 |
+
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
10 |
+
|
11 |
+
|
12 |
+
load_dotenv() # read local .env file
|
13 |
+
model = os.getenv("OPENAI_MODEL") or "gpt-4-turbo"
|
14 |
+
client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"))
|
15 |
+
|
16 |
+
|
17 |
+
MAX_TOKENS_PER_CHUNK = (
|
18 |
+
1000 # if text is more than this many tokens, we'll break it up into
|
19 |
+
)
|
20 |
+
# discrete chunks to translate one chunk at a time
|
21 |
+
|
22 |
+
|
23 |
+
def get_completion(
|
24 |
+
prompt: str,
|
25 |
+
system_message: str = "You are a helpful assistant.",
|
26 |
+
model: str = model,
|
27 |
+
temperature: float = 0.3,
|
28 |
+
json_mode: bool = False,
|
29 |
+
) -> Union[str, dict]:
|
30 |
+
"""
|
31 |
+
Generate a completion using the OpenAI API.
|
32 |
+
|
33 |
+
Args:
|
34 |
+
prompt (str): The user's prompt or query.
|
35 |
+
system_message (str, optional): The system message to set the context for the assistant.
|
36 |
+
Defaults to "You are a helpful assistant.".
|
37 |
+
model (str, optional): The name of the OpenAI model to use for generating the completion.
|
38 |
+
Defaults to "gpt-4-turbo".
|
39 |
+
temperature (float, optional): The sampling temperature for controlling the randomness of the generated text.
|
40 |
+
Defaults to 0.3.
|
41 |
+
json_mode (bool, optional): Whether to return the response in JSON format.
|
42 |
+
Defaults to False.
|
43 |
+
|
44 |
+
Returns:
|
45 |
+
Union[str, dict]: The generated completion.
|
46 |
+
If json_mode is True, returns the complete API response as a dictionary.
|
47 |
+
If json_mode is False, returns the generated text as a string.
|
48 |
+
"""
|
49 |
+
|
50 |
+
if json_mode:
|
51 |
+
response = client.chat.completions.create(
|
52 |
+
model=model,
|
53 |
+
temperature=temperature,
|
54 |
+
top_p=1,
|
55 |
+
response_format={"type": "json_object"},
|
56 |
+
messages=[
|
57 |
+
{"role": "system", "content": system_message},
|
58 |
+
{"role": "user", "content": prompt},
|
59 |
+
],
|
60 |
+
)
|
61 |
+
return response.choices[0].message.content
|
62 |
+
else:
|
63 |
+
response = client.chat.completions.create(
|
64 |
+
model=model,
|
65 |
+
temperature=temperature,
|
66 |
+
top_p=1,
|
67 |
+
messages=[
|
68 |
+
{"role": "system", "content": system_message},
|
69 |
+
{"role": "user", "content": prompt},
|
70 |
+
],
|
71 |
+
)
|
72 |
+
return response.choices[0].message.content
|
73 |
+
|
74 |
+
|
75 |
+
def one_chunk_initial_translation(
|
76 |
+
source_lang: str, target_lang: str, source_text: str
|
77 |
+
) -> str:
|
78 |
+
"""
|
79 |
+
Translate the entire text as one chunk using an LLM.
|
80 |
+
|
81 |
+
Args:
|
82 |
+
source_lang (str): The source language of the text.
|
83 |
+
target_lang (str): The target language for translation.
|
84 |
+
source_text (str): The text to be translated.
|
85 |
+
|
86 |
+
Returns:
|
87 |
+
str: The translated text.
|
88 |
+
"""
|
89 |
+
|
90 |
+
system_message = f"You are an expert linguist, specializing in translation from {source_lang} to {target_lang}."
|
91 |
+
|
92 |
+
translation_prompt = f"""This is an {source_lang} to {target_lang} translation, please provide the {target_lang} translation for this text. \
|
93 |
+
Do not provide any explanations or text apart from the translation.
|
94 |
+
{source_lang}: {source_text}
|
95 |
+
|
96 |
+
{target_lang}:"""
|
97 |
+
|
98 |
+
prompt = translation_prompt.format(source_text=source_text)
|
99 |
+
|
100 |
+
translation = get_completion(prompt, system_message=system_message)
|
101 |
+
|
102 |
+
return translation
|
103 |
+
|
104 |
+
|
105 |
+
def one_chunk_reflect_on_translation(
|
106 |
+
source_lang: str,
|
107 |
+
target_lang: str,
|
108 |
+
source_text: str,
|
109 |
+
translation_1: str,
|
110 |
+
country: str = "",
|
111 |
+
) -> str:
|
112 |
+
"""
|
113 |
+
Use an LLM to reflect on the translation, treating the entire text as one chunk.
|
114 |
+
|
115 |
+
Args:
|
116 |
+
source_lang (str): The source language of the text.
|
117 |
+
target_lang (str): The target language of the translation.
|
118 |
+
source_text (str): The original text in the source language.
|
119 |
+
translation_1 (str): The initial translation of the source text.
|
120 |
+
country (str): Country specified for target language.
|
121 |
+
|
122 |
+
Returns:
|
123 |
+
str: The LLM's reflection on the translation, providing constructive criticism and suggestions for improvement.
|
124 |
+
"""
|
125 |
+
|
126 |
+
system_message = f"You are an expert linguist specializing in translation from {source_lang} to {target_lang}. \
|
127 |
+
You will be provided with a source text and its translation and your goal is to improve the translation."
|
128 |
+
|
129 |
+
if country != "":
|
130 |
+
reflection_prompt = f"""Your task is to carefully read a source text and a translation from {source_lang} to {target_lang}, and then give constructive criticism and helpful suggestions to improve the translation. \
|
131 |
+
The final style and tone of the translation should match the style of {target_lang} colloquially spoken in {country}.
|
132 |
+
|
133 |
+
The source text and initial translation, delimited by XML tags <SOURCE_TEXT></SOURCE_TEXT> and <TRANSLATION></TRANSLATION>, are as follows:
|
134 |
+
|
135 |
+
<SOURCE_TEXT>
|
136 |
+
{source_text}
|
137 |
+
</SOURCE_TEXT>
|
138 |
+
|
139 |
+
<TRANSLATION>
|
140 |
+
{translation_1}
|
141 |
+
</TRANSLATION>
|
142 |
+
|
143 |
+
When writing suggestions, pay attention to whether there are ways to improve the translation's \n\
|
144 |
+
(i) accuracy (by correcting errors of addition, mistranslation, omission, or untranslated text),\n\
|
145 |
+
(ii) fluency (by applying {target_lang} grammar, spelling and punctuation rules, and ensuring there are no unnecessary repetitions),\n\
|
146 |
+
(iii) style (by ensuring the translations reflect the style of the source text and takes into account any cultural context),\n\
|
147 |
+
(iv) terminology (by ensuring terminology use is consistent and reflects the source text domain; and by only ensuring you use equivalent idioms {target_lang}).\n\
|
148 |
+
|
149 |
+
Write a list of specific, helpful and constructive suggestions for improving the translation.
|
150 |
+
Each suggestion should address one specific part of the translation.
|
151 |
+
Output only the suggestions and nothing else."""
|
152 |
+
|
153 |
+
else:
|
154 |
+
reflection_prompt = f"""Your task is to carefully read a source text and a translation from {source_lang} to {target_lang}, and then give constructive criticism and helpful suggestions to improve the translation. \
|
155 |
+
|
156 |
+
The source text and initial translation, delimited by XML tags <SOURCE_TEXT></SOURCE_TEXT> and <TRANSLATION></TRANSLATION>, are as follows:
|
157 |
+
|
158 |
+
<SOURCE_TEXT>
|
159 |
+
{source_text}
|
160 |
+
</SOURCE_TEXT>
|
161 |
+
|
162 |
+
<TRANSLATION>
|
163 |
+
{translation_1}
|
164 |
+
</TRANSLATION>
|
165 |
+
|
166 |
+
When writing suggestions, pay attention to whether there are ways to improve the translation's \n\
|
167 |
+
(i) accuracy (by correcting errors of addition, mistranslation, omission, or untranslated text),\n\
|
168 |
+
(ii) fluency (by applying {target_lang} grammar, spelling and punctuation rules, and ensuring there are no unnecessary repetitions),\n\
|
169 |
+
(iii) style (by ensuring the translations reflect the style of the source text and takes into account any cultural context),\n\
|
170 |
+
(iv) terminology (by ensuring terminology use is consistent and reflects the source text domain; and by only ensuring you use equivalent idioms {target_lang}).\n\
|
171 |
+
|
172 |
+
Write a list of specific, helpful and constructive suggestions for improving the translation.
|
173 |
+
Each suggestion should address one specific part of the translation.
|
174 |
+
Output only the suggestions and nothing else."""
|
175 |
+
|
176 |
+
prompt = reflection_prompt.format(
|
177 |
+
source_lang=source_lang,
|
178 |
+
target_lang=target_lang,
|
179 |
+
source_text=source_text,
|
180 |
+
translation_1=translation_1,
|
181 |
+
)
|
182 |
+
reflection = get_completion(prompt, system_message=system_message)
|
183 |
+
return reflection
|
184 |
+
|
185 |
+
|
186 |
+
def one_chunk_improve_translation(
|
187 |
+
source_lang: str,
|
188 |
+
target_lang: str,
|
189 |
+
source_text: str,
|
190 |
+
translation_1: str,
|
191 |
+
reflection: str,
|
192 |
+
) -> str:
|
193 |
+
"""
|
194 |
+
Use the reflection to improve the translation, treating the entire text as one chunk.
|
195 |
+
|
196 |
+
Args:
|
197 |
+
source_lang (str): The source language of the text.
|
198 |
+
target_lang (str): The target language for the translation.
|
199 |
+
source_text (str): The original text in the source language.
|
200 |
+
translation_1 (str): The initial translation of the source text.
|
201 |
+
reflection (str): Expert suggestions and constructive criticism for improving the translation.
|
202 |
+
|
203 |
+
Returns:
|
204 |
+
str: The improved translation based on the expert suggestions.
|
205 |
+
"""
|
206 |
+
|
207 |
+
system_message = f"You are an expert linguist, specializing in translation editing from {source_lang} to {target_lang}."
|
208 |
+
|
209 |
+
prompt = f"""Your task is to carefully read, then edit, a translation from {source_lang} to {target_lang}, taking into
|
210 |
+
account a list of expert suggestions and constructive criticisms.
|
211 |
+
|
212 |
+
The source text, the initial translation, and the expert linguist suggestions are delimited by XML tags <SOURCE_TEXT></SOURCE_TEXT>, <TRANSLATION></TRANSLATION> and <EXPERT_SUGGESTIONS></EXPERT_SUGGESTIONS> \
|
213 |
+
as follows:
|
214 |
+
|
215 |
+
<SOURCE_TEXT>
|
216 |
+
{source_text}
|
217 |
+
</SOURCE_TEXT>
|
218 |
+
|
219 |
+
<TRANSLATION>
|
220 |
+
{translation_1}
|
221 |
+
</TRANSLATION>
|
222 |
+
|
223 |
+
<EXPERT_SUGGESTIONS>
|
224 |
+
{reflection}
|
225 |
+
</EXPERT_SUGGESTIONS>
|
226 |
+
|
227 |
+
Please take into account the expert suggestions when editing the translation. Edit the translation by ensuring:
|
228 |
+
|
229 |
+
(i) accuracy (by correcting errors of addition, mistranslation, omission, or untranslated text),
|
230 |
+
(ii) fluency (by applying {target_lang} grammar, spelling and punctuation rules and ensuring there are no unnecessary repetitions), \
|
231 |
+
(iii) style (by ensuring the translations reflect the style of the source text)
|
232 |
+
(iv) terminology (inappropriate for context, inconsistent use), or
|
233 |
+
(v) other errors.
|
234 |
+
|
235 |
+
Output only the new translation and nothing else."""
|
236 |
+
|
237 |
+
translation_2 = get_completion(prompt, system_message)
|
238 |
+
|
239 |
+
return translation_2
|
240 |
+
|
241 |
+
|
242 |
+
def one_chunk_translate_text(
|
243 |
+
source_lang: str, target_lang: str, source_text: str, country: str = ""
|
244 |
+
) -> str:
|
245 |
+
"""
|
246 |
+
Translate a single chunk of text from the source language to the target language.
|
247 |
+
|
248 |
+
This function performs a two-step translation process:
|
249 |
+
1. Get an initial translation of the source text.
|
250 |
+
2. Reflect on the initial translation and generate an improved translation.
|
251 |
+
|
252 |
+
Args:
|
253 |
+
source_lang (str): The source language of the text.
|
254 |
+
target_lang (str): The target language for the translation.
|
255 |
+
source_text (str): The text to be translated.
|
256 |
+
country (str): Country specified for target language.
|
257 |
+
Returns:
|
258 |
+
str: The improved translation of the source text.
|
259 |
+
"""
|
260 |
+
translation_1 = one_chunk_initial_translation(
|
261 |
+
source_lang, target_lang, source_text
|
262 |
+
)
|
263 |
+
|
264 |
+
reflection = one_chunk_reflect_on_translation(
|
265 |
+
source_lang, target_lang, source_text, translation_1, country
|
266 |
+
)
|
267 |
+
translation_2 = one_chunk_improve_translation(
|
268 |
+
source_lang, target_lang, source_text, translation_1, reflection
|
269 |
+
)
|
270 |
+
|
271 |
+
return translation_2
|
272 |
+
|
273 |
+
|
274 |
+
def num_tokens_in_string(
|
275 |
+
input_str: str, encoding_name: str = "cl100k_base"
|
276 |
+
) -> int:
|
277 |
+
"""
|
278 |
+
Calculate the number of tokens in a given string using a specified encoding.
|
279 |
+
|
280 |
+
Args:
|
281 |
+
str (str): The input string to be tokenized.
|
282 |
+
encoding_name (str, optional): The name of the encoding to use. Defaults to "cl100k_base",
|
283 |
+
which is the most commonly used encoder (used by GPT-4).
|
284 |
+
|
285 |
+
Returns:
|
286 |
+
int: The number of tokens in the input string.
|
287 |
+
|
288 |
+
Example:
|
289 |
+
>>> text = "Hello, how are you?"
|
290 |
+
>>> num_tokens = num_tokens_in_string(text)
|
291 |
+
>>> print(num_tokens)
|
292 |
+
5
|
293 |
+
"""
|
294 |
+
encoding = tiktoken.get_encoding(encoding_name)
|
295 |
+
num_tokens = len(encoding.encode(input_str))
|
296 |
+
return num_tokens
|
297 |
+
|
298 |
+
|
299 |
+
def multichunk_initial_translation(
|
300 |
+
source_lang: str, target_lang: str, source_text_chunks: List[str]
|
301 |
+
) -> List[str]:
|
302 |
+
"""
|
303 |
+
Translate a text in multiple chunks from the source language to the target language.
|
304 |
+
|
305 |
+
Args:
|
306 |
+
source_lang (str): The source language of the text.
|
307 |
+
target_lang (str): The target language for translation.
|
308 |
+
source_text_chunks (List[str]): A list of text chunks to be translated.
|
309 |
+
|
310 |
+
Returns:
|
311 |
+
List[str]: A list of translated text chunks.
|
312 |
+
"""
|
313 |
+
|
314 |
+
system_message = f"You are an expert linguist, specializing in translation from {source_lang} to {target_lang}."
|
315 |
+
|
316 |
+
translation_prompt = """Your task is provide a professional translation from {source_lang} to {target_lang} of PART of a text.
|
317 |
+
|
318 |
+
The source text is below, delimited by XML tags <SOURCE_TEXT> and </SOURCE_TEXT>. Translate only the part within the source text
|
319 |
+
delimited by <TRANSLATE_THIS> and </TRANSLATE_THIS>. You can use the rest of the source text as context, but do not translate any
|
320 |
+
of the other text. Do not output anything other than the translation of the indicated part of the text.
|
321 |
+
|
322 |
+
<SOURCE_TEXT>
|
323 |
+
{tagged_text}
|
324 |
+
</SOURCE_TEXT>
|
325 |
+
|
326 |
+
To reiterate, you should translate only this part of the text, shown here again between <TRANSLATE_THIS> and </TRANSLATE_THIS>:
|
327 |
+
<TRANSLATE_THIS>
|
328 |
+
{chunk_to_translate}
|
329 |
+
</TRANSLATE_THIS>
|
330 |
+
|
331 |
+
Output only the translation of the portion you are asked to translate, and nothing else.
|
332 |
+
"""
|
333 |
+
|
334 |
+
translation_chunks = []
|
335 |
+
for i in range(len(source_text_chunks)):
|
336 |
+
# Will translate chunk i
|
337 |
+
tagged_text = (
|
338 |
+
"".join(source_text_chunks[0:i])
|
339 |
+
+ "<TRANSLATE_THIS>"
|
340 |
+
+ source_text_chunks[i]
|
341 |
+
+ "</TRANSLATE_THIS>"
|
342 |
+
+ "".join(source_text_chunks[i + 1 :])
|
343 |
+
)
|
344 |
+
|
345 |
+
prompt = translation_prompt.format(
|
346 |
+
source_lang=source_lang,
|
347 |
+
target_lang=target_lang,
|
348 |
+
tagged_text=tagged_text,
|
349 |
+
chunk_to_translate=source_text_chunks[i],
|
350 |
+
)
|
351 |
+
|
352 |
+
translation = get_completion(prompt, system_message=system_message)
|
353 |
+
translation_chunks.append(translation)
|
354 |
+
|
355 |
+
return translation_chunks
|
356 |
+
|
357 |
+
|
358 |
+
def multichunk_reflect_on_translation(
|
359 |
+
source_lang: str,
|
360 |
+
target_lang: str,
|
361 |
+
source_text_chunks: List[str],
|
362 |
+
translation_1_chunks: List[str],
|
363 |
+
country: str = "",
|
364 |
+
) -> List[str]:
|
365 |
+
"""
|
366 |
+
Provides constructive criticism and suggestions for improving a partial translation.
|
367 |
+
|
368 |
+
Args:
|
369 |
+
source_lang (str): The source language of the text.
|
370 |
+
target_lang (str): The target language of the translation.
|
371 |
+
source_text_chunks (List[str]): The source text divided into chunks.
|
372 |
+
translation_1_chunks (List[str]): The translated chunks corresponding to the source text chunks.
|
373 |
+
country (str): Country specified for target language.
|
374 |
+
|
375 |
+
Returns:
|
376 |
+
List[str]: A list of reflections containing suggestions for improving each translated chunk.
|
377 |
+
"""
|
378 |
+
|
379 |
+
system_message = f"You are an expert linguist specializing in translation from {source_lang} to {target_lang}. \
|
380 |
+
You will be provided with a source text and its translation and your goal is to improve the translation."
|
381 |
+
|
382 |
+
if country != "":
|
383 |
+
reflection_prompt = """Your task is to carefully read a source text and part of a translation of that text from {source_lang} to {target_lang}, and then give constructive criticism and helpful suggestions for improving the translation.
|
384 |
+
The final style and tone of the translation should match the style of {target_lang} colloquially spoken in {country}.
|
385 |
+
|
386 |
+
The source text is below, delimited by XML tags <SOURCE_TEXT> and </SOURCE_TEXT>, and the part that has been translated
|
387 |
+
is delimited by <TRANSLATE_THIS> and </TRANSLATE_THIS> within the source text. You can use the rest of the source text
|
388 |
+
as context for critiquing the translated part.
|
389 |
+
|
390 |
+
<SOURCE_TEXT>
|
391 |
+
{tagged_text}
|
392 |
+
</SOURCE_TEXT>
|
393 |
+
|
394 |
+
To reiterate, only part of the text is being translated, shown here again between <TRANSLATE_THIS> and </TRANSLATE_THIS>:
|
395 |
+
<TRANSLATE_THIS>
|
396 |
+
{chunk_to_translate}
|
397 |
+
</TRANSLATE_THIS>
|
398 |
+
|
399 |
+
The translation of the indicated part, delimited below by <TRANSLATION> and </TRANSLATION>, is as follows:
|
400 |
+
<TRANSLATION>
|
401 |
+
{translation_1_chunk}
|
402 |
+
</TRANSLATION>
|
403 |
+
|
404 |
+
When writing suggestions, pay attention to whether there are ways to improve the translation's:\n\
|
405 |
+
(i) accuracy (by correcting errors of addition, mistranslation, omission, or untranslated text),\n\
|
406 |
+
(ii) fluency (by applying {target_lang} grammar, spelling and punctuation rules, and ensuring there are no unnecessary repetitions),\n\
|
407 |
+
(iii) style (by ensuring the translations reflect the style of the source text and takes into account any cultural context),\n\
|
408 |
+
(iv) terminology (by ensuring terminology use is consistent and reflects the source text domain; and by only ensuring you use equivalent idioms {target_lang}).\n\
|
409 |
+
|
410 |
+
Write a list of specific, helpful and constructive suggestions for improving the translation.
|
411 |
+
Each suggestion should address one specific part of the translation.
|
412 |
+
Output only the suggestions and nothing else."""
|
413 |
+
|
414 |
+
else:
|
415 |
+
reflection_prompt = """Your task is to carefully read a source text and part of a translation of that text from {source_lang} to {target_lang}, and then give constructive criticism and helpful suggestions for improving the translation.
|
416 |
+
|
417 |
+
The source text is below, delimited by XML tags <SOURCE_TEXT> and </SOURCE_TEXT>, and the part that has been translated
|
418 |
+
is delimited by <TRANSLATE_THIS> and </TRANSLATE_THIS> within the source text. You can use the rest of the source text
|
419 |
+
as context for critiquing the translated part.
|
420 |
+
|
421 |
+
<SOURCE_TEXT>
|
422 |
+
{tagged_text}
|
423 |
+
</SOURCE_TEXT>
|
424 |
+
|
425 |
+
To reiterate, only part of the text is being translated, shown here again between <TRANSLATE_THIS> and </TRANSLATE_THIS>:
|
426 |
+
<TRANSLATE_THIS>
|
427 |
+
{chunk_to_translate}
|
428 |
+
</TRANSLATE_THIS>
|
429 |
+
|
430 |
+
The translation of the indicated part, delimited below by <TRANSLATION> and </TRANSLATION>, is as follows:
|
431 |
+
<TRANSLATION>
|
432 |
+
{translation_1_chunk}
|
433 |
+
</TRANSLATION>
|
434 |
+
|
435 |
+
When writing suggestions, pay attention to whether there are ways to improve the translation's:\n\
|
436 |
+
(i) accuracy (by correcting errors of addition, mistranslation, omission, or untranslated text),\n\
|
437 |
+
(ii) fluency (by applying {target_lang} grammar, spelling and punctuation rules, and ensuring there are no unnecessary repetitions),\n\
|
438 |
+
(iii) style (by ensuring the translations reflect the style of the source text and takes into account any cultural context),\n\
|
439 |
+
(iv) terminology (by ensuring terminology use is consistent and reflects the source text domain; and by only ensuring you use equivalent idioms {target_lang}).\n\
|
440 |
+
|
441 |
+
Write a list of specific, helpful and constructive suggestions for improving the translation.
|
442 |
+
Each suggestion should address one specific part of the translation.
|
443 |
+
Output only the suggestions and nothing else."""
|
444 |
+
|
445 |
+
reflection_chunks = []
|
446 |
+
for i in range(len(source_text_chunks)):
|
447 |
+
# Will translate chunk i
|
448 |
+
tagged_text = (
|
449 |
+
"".join(source_text_chunks[0:i])
|
450 |
+
+ "<TRANSLATE_THIS>"
|
451 |
+
+ source_text_chunks[i]
|
452 |
+
+ "</TRANSLATE_THIS>"
|
453 |
+
+ "".join(source_text_chunks[i + 1 :])
|
454 |
+
)
|
455 |
+
if country != "":
|
456 |
+
prompt = reflection_prompt.format(
|
457 |
+
source_lang=source_lang,
|
458 |
+
target_lang=target_lang,
|
459 |
+
tagged_text=tagged_text,
|
460 |
+
chunk_to_translate=source_text_chunks[i],
|
461 |
+
translation_1_chunk=translation_1_chunks[i],
|
462 |
+
country=country,
|
463 |
+
)
|
464 |
+
else:
|
465 |
+
prompt = reflection_prompt.format(
|
466 |
+
source_lang=source_lang,
|
467 |
+
target_lang=target_lang,
|
468 |
+
tagged_text=tagged_text,
|
469 |
+
chunk_to_translate=source_text_chunks[i],
|
470 |
+
translation_1_chunk=translation_1_chunks[i],
|
471 |
+
)
|
472 |
+
|
473 |
+
reflection = get_completion(prompt, system_message=system_message)
|
474 |
+
reflection_chunks.append(reflection)
|
475 |
+
|
476 |
+
return reflection_chunks
|
477 |
+
|
478 |
+
|
479 |
+
def multichunk_improve_translation(
|
480 |
+
source_lang: str,
|
481 |
+
target_lang: str,
|
482 |
+
source_text_chunks: List[str],
|
483 |
+
translation_1_chunks: List[str],
|
484 |
+
reflection_chunks: List[str],
|
485 |
+
) -> List[str]:
|
486 |
+
"""
|
487 |
+
Improves the translation of a text from source language to target language by considering expert suggestions.
|
488 |
+
|
489 |
+
Args:
|
490 |
+
source_lang (str): The source language of the text.
|
491 |
+
target_lang (str): The target language for translation.
|
492 |
+
source_text_chunks (List[str]): The source text divided into chunks.
|
493 |
+
translation_1_chunks (List[str]): The initial translation of each chunk.
|
494 |
+
reflection_chunks (List[str]): Expert suggestions for improving each translated chunk.
|
495 |
+
|
496 |
+
Returns:
|
497 |
+
List[str]: The improved translation of each chunk.
|
498 |
+
"""
|
499 |
+
|
500 |
+
system_message = f"You are an expert linguist, specializing in translation editing from {source_lang} to {target_lang}."
|
501 |
+
|
502 |
+
improvement_prompt = """Your task is to carefully read, then improve, a translation from {source_lang} to {target_lang}, taking into
|
503 |
+
account a set of expert suggestions and constructive critisms. Below, the source text, initial translation, and expert suggestions are provided.
|
504 |
+
|
505 |
+
The source text is below, delimited by XML tags <SOURCE_TEXT> and </SOURCE_TEXT>, and the part that has been translated
|
506 |
+
is delimited by <TRANSLATE_THIS> and </TRANSLATE_THIS> within the source text. You can use the rest of the source text
|
507 |
+
as context, but need to provide a translation only of the part indicated by <TRANSLATE_THIS> and </TRANSLATE_THIS>.
|
508 |
+
|
509 |
+
<SOURCE_TEXT>
|
510 |
+
{tagged_text}
|
511 |
+
</SOURCE_TEXT>
|
512 |
+
|
513 |
+
To reiterate, only part of the text is being translated, shown here again between <TRANSLATE_THIS> and </TRANSLATE_THIS>:
|
514 |
+
<TRANSLATE_THIS>
|
515 |
+
{chunk_to_translate}
|
516 |
+
</TRANSLATE_THIS>
|
517 |
+
|
518 |
+
The translation of the indicated part, delimited below by <TRANSLATION> and </TRANSLATION>, is as follows:
|
519 |
+
<TRANSLATION>
|
520 |
+
{translation_1_chunk}
|
521 |
+
</TRANSLATION>
|
522 |
+
|
523 |
+
The expert translations of the indicated part, delimited below by <EXPERT_SUGGESTIONS> and </EXPERT_SUGGESTIONS>, is as follows:
|
524 |
+
<EXPERT_SUGGESTIONS>
|
525 |
+
{reflection_chunk}
|
526 |
+
</EXPERT_SUGGESTIONS>
|
527 |
+
|
528 |
+
Taking into account the expert suggestions rewrite the translation to improve it, paying attention
|
529 |
+
to whether there are ways to improve the translation's
|
530 |
+
|
531 |
+
(i) accuracy (by correcting errors of addition, mistranslation, omission, or untranslated text),
|
532 |
+
(ii) fluency (by applying {target_lang} grammar, spelling and punctuation rules and ensuring there are no unnecessary repetitions), \
|
533 |
+
(iii) style (by ensuring the translations reflect the style of the source text)
|
534 |
+
(iv) terminology (inappropriate for context, inconsistent use), or
|
535 |
+
(v) other errors.
|
536 |
+
|
537 |
+
Output only the new translation of the indicated part and nothing else."""
|
538 |
+
|
539 |
+
translation_2_chunks = []
|
540 |
+
for i in range(len(source_text_chunks)):
|
541 |
+
# Will translate chunk i
|
542 |
+
tagged_text = (
|
543 |
+
"".join(source_text_chunks[0:i])
|
544 |
+
+ "<TRANSLATE_THIS>"
|
545 |
+
+ source_text_chunks[i]
|
546 |
+
+ "</TRANSLATE_THIS>"
|
547 |
+
+ "".join(source_text_chunks[i + 1 :])
|
548 |
+
)
|
549 |
+
|
550 |
+
prompt = improvement_prompt.format(
|
551 |
+
source_lang=source_lang,
|
552 |
+
target_lang=target_lang,
|
553 |
+
tagged_text=tagged_text,
|
554 |
+
chunk_to_translate=source_text_chunks[i],
|
555 |
+
translation_1_chunk=translation_1_chunks[i],
|
556 |
+
reflection_chunk=reflection_chunks[i],
|
557 |
+
)
|
558 |
+
|
559 |
+
translation_2 = get_completion(prompt, system_message=system_message)
|
560 |
+
translation_2_chunks.append(translation_2)
|
561 |
+
|
562 |
+
return translation_2_chunks
|
563 |
+
|
564 |
+
|
565 |
+
def multichunk_translation(
|
566 |
+
source_lang, target_lang, source_text_chunks, country: str = ""
|
567 |
+
):
|
568 |
+
"""
|
569 |
+
Improves the translation of multiple text chunks based on the initial translation and reflection.
|
570 |
+
|
571 |
+
Args:
|
572 |
+
source_lang (str): The source language of the text chunks.
|
573 |
+
target_lang (str): The target language for translation.
|
574 |
+
source_text_chunks (List[str]): The list of source text chunks to be translated.
|
575 |
+
translation_1_chunks (List[str]): The list of initial translations for each source text chunk.
|
576 |
+
reflection_chunks (List[str]): The list of reflections on the initial translations.
|
577 |
+
country (str): Country specified for target language
|
578 |
+
Returns:
|
579 |
+
List[str]: The list of improved translations for each source text chunk.
|
580 |
+
"""
|
581 |
+
|
582 |
+
translation_1_chunks = multichunk_initial_translation(
|
583 |
+
source_lang, target_lang, source_text_chunks
|
584 |
+
)
|
585 |
+
|
586 |
+
reflection_chunks = multichunk_reflect_on_translation(
|
587 |
+
source_lang,
|
588 |
+
target_lang,
|
589 |
+
source_text_chunks,
|
590 |
+
translation_1_chunks,
|
591 |
+
country,
|
592 |
+
)
|
593 |
+
|
594 |
+
translation_2_chunks = multichunk_improve_translation(
|
595 |
+
source_lang,
|
596 |
+
target_lang,
|
597 |
+
source_text_chunks,
|
598 |
+
translation_1_chunks,
|
599 |
+
reflection_chunks,
|
600 |
+
)
|
601 |
+
|
602 |
+
return translation_2_chunks
|
603 |
+
|
604 |
+
|
605 |
+
def calculate_chunk_size(token_count: int, token_limit: int) -> int:
|
606 |
+
"""
|
607 |
+
Calculate the chunk size based on the token count and token limit.
|
608 |
+
|
609 |
+
Args:
|
610 |
+
token_count (int): The total number of tokens.
|
611 |
+
token_limit (int): The maximum number of tokens allowed per chunk.
|
612 |
+
|
613 |
+
Returns:
|
614 |
+
int: The calculated chunk size.
|
615 |
+
|
616 |
+
Description:
|
617 |
+
This function calculates the chunk size based on the given token count and token limit.
|
618 |
+
If the token count is less than or equal to the token limit, the function returns the token count as the chunk size.
|
619 |
+
Otherwise, it calculates the number of chunks needed to accommodate all the tokens within the token limit.
|
620 |
+
The chunk size is determined by dividing the token limit by the number of chunks.
|
621 |
+
If there are remaining tokens after dividing the token count by the token limit,
|
622 |
+
the chunk size is adjusted by adding the remaining tokens divided by the number of chunks.
|
623 |
+
|
624 |
+
Example:
|
625 |
+
>>> calculate_chunk_size(1000, 500)
|
626 |
+
500
|
627 |
+
>>> calculate_chunk_size(1530, 500)
|
628 |
+
389
|
629 |
+
>>> calculate_chunk_size(2242, 500)
|
630 |
+
496
|
631 |
+
"""
|
632 |
+
|
633 |
+
if token_count <= token_limit:
|
634 |
+
return token_count
|
635 |
+
|
636 |
+
num_chunks = (token_count + token_limit - 1) // token_limit
|
637 |
+
chunk_size = token_count // num_chunks
|
638 |
+
|
639 |
+
remaining_tokens = token_count % token_limit
|
640 |
+
if remaining_tokens > 0:
|
641 |
+
chunk_size += remaining_tokens // num_chunks
|
642 |
+
|
643 |
+
return chunk_size
|
644 |
+
|
645 |
+
|
646 |
+
def translate(
|
647 |
+
source_lang,
|
648 |
+
target_lang,
|
649 |
+
source_text,
|
650 |
+
country,
|
651 |
+
max_tokens=MAX_TOKENS_PER_CHUNK,
|
652 |
+
):
|
653 |
+
"""Translate the source_text from source_lang to target_lang."""
|
654 |
+
|
655 |
+
num_tokens_in_text = num_tokens_in_string(source_text)
|
656 |
+
|
657 |
+
ic(num_tokens_in_text)
|
658 |
+
|
659 |
+
if num_tokens_in_text < max_tokens:
|
660 |
+
ic("Translating text as single chunk")
|
661 |
+
|
662 |
+
final_translation = one_chunk_translate_text(
|
663 |
+
source_lang, target_lang, source_text, country
|
664 |
+
)
|
665 |
+
|
666 |
+
return final_translation
|
667 |
+
|
668 |
+
else:
|
669 |
+
ic("Translating text as multiple chunks")
|
670 |
+
|
671 |
+
token_size = calculate_chunk_size(
|
672 |
+
token_count=num_tokens_in_text, token_limit=max_tokens
|
673 |
+
)
|
674 |
+
|
675 |
+
ic(token_size)
|
676 |
+
|
677 |
+
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
|
678 |
+
model_name="gpt-4",
|
679 |
+
chunk_size=token_size,
|
680 |
+
chunk_overlap=0,
|
681 |
+
)
|
682 |
+
|
683 |
+
source_text_chunks = text_splitter.split_text(source_text)
|
684 |
+
|
685 |
+
translation_2_chunks = multichunk_translation(
|
686 |
+
source_lang, target_lang, source_text_chunks, country
|
687 |
+
)
|
688 |
+
|
689 |
+
return "".join(translation_2_chunks)
|