riu-rd commited on
Commit
af74f3c
·
verified ·
1 Parent(s): 351a5c2

Upload 17 files

Browse files
api.py CHANGED
@@ -38,7 +38,7 @@ app.add_middleware(
38
  async def docs():
39
  return RedirectResponse(url="/docs")
40
 
41
- @app.post("/audio/whisper", response_model=Dict[str, str])
42
  async def audio_whisper(audio: UploadFile = File(...)):
43
  """
44
  Transcribes and translates an audio file using OpenAI's Whisper model.
@@ -64,7 +64,7 @@ async def audio_whisper(audio: UploadFile = File(...)):
64
  # Catch exceptions from the audio processing service or file reading
65
  raise HTTPException(status_code=500, detail=f"Audio processing failed: {str(e)}")
66
 
67
- @app.post("/audio/gemini", response_model=Dict[str, str])
68
  async def audio_gemini(audio: UploadFile = File(...)):
69
  """
70
  Receives an audio file, transcribes it, and translates the transcription
 
38
  async def docs():
39
  return RedirectResponse(url="/docs")
40
 
41
+ @app.post("/audio/whisper", response_model=Dict[str, Any])
42
  async def audio_whisper(audio: UploadFile = File(...)):
43
  """
44
  Transcribes and translates an audio file using OpenAI's Whisper model.
 
64
  # Catch exceptions from the audio processing service or file reading
65
  raise HTTPException(status_code=500, detail=f"Audio processing failed: {str(e)}")
66
 
67
+ @app.post("/audio/gemini", response_model=Dict[str, Any])
68
  async def audio_gemini(audio: UploadFile = File(...)):
69
  """
70
  Receives an audio file, transcribes it, and translates the transcription
requirements.txt CHANGED
@@ -26,4 +26,5 @@ soundfile
26
  openai-whisper
27
  pydantic
28
  langchain-google-genai
29
- langchain
 
 
26
  openai-whisper
27
  pydantic
28
  langchain-google-genai
29
+ langchain
30
+ tqdm
services/__pycache__/audio_gemini.cpython-311.pyc CHANGED
Binary files a/services/__pycache__/audio_gemini.cpython-311.pyc and b/services/__pycache__/audio_gemini.cpython-311.pyc differ
 
services/__pycache__/audio_whisper.cpython-311.pyc CHANGED
Binary files a/services/__pycache__/audio_whisper.cpython-311.pyc and b/services/__pycache__/audio_whisper.cpython-311.pyc differ
 
services/__pycache__/text_processor.cpython-311.pyc CHANGED
Binary files a/services/__pycache__/text_processor.cpython-311.pyc and b/services/__pycache__/text_processor.cpython-311.pyc differ
 
services/audio_gemini.py CHANGED
@@ -4,6 +4,13 @@ from typing import Dict
4
  import google.genai as genai
5
  from dotenv import load_dotenv
6
  from google.genai.types import Part
 
 
 
 
 
 
 
7
 
8
  # Load environment variables from a .env file in the root directory
9
  load_dotenv()
@@ -59,7 +66,7 @@ def _translate_to_english(text: str) -> str:
59
  return resp.text.strip() # type: ignore
60
 
61
 
62
- def process_audio_with_gemini(audio_bytes: bytes) -> Dict[str, str]:
63
  """
64
  Processes an audio file by first transcribing it and then translating the
65
  resulting text to English using the Gemini model.
@@ -84,8 +91,12 @@ def process_audio_with_gemini(audio_bytes: bytes) -> Dict[str, str]:
84
  translation = ""
85
  if transcription:
86
  translation = _translate_to_english(transcription)
87
-
88
- return {"transcription": transcription, "translation": translation}
 
 
 
 
89
  except Exception as e:
90
  # Re-raise the exception with more context to be caught by the API endpoint
91
  raise Exception(f"Error processing audio with Gemini: {str(e)}")
 
4
  import google.genai as genai
5
  from dotenv import load_dotenv
6
  from google.genai.types import Part
7
+ from pydantic import BaseModel
8
+
9
+ from services.text_processor import process_text_to_insight
10
+
11
+ # Add the TextRequest model definition here or import it
12
+ class TextRequest(BaseModel):
13
+ text: str
14
 
15
  # Load environment variables from a .env file in the root directory
16
  load_dotenv()
 
66
  return resp.text.strip() # type: ignore
67
 
68
 
69
+ def process_audio_with_gemini(audio_bytes: bytes) -> Dict[str, any]:
70
  """
71
  Processes an audio file by first transcribing it and then translating the
72
  resulting text to English using the Gemini model.
 
91
  translation = ""
92
  if transcription:
93
  translation = _translate_to_english(transcription)
94
+
95
+ # Step 3: Generate insights using TextRequest object
96
+ text_request = TextRequest(text=transcription)
97
+ audio_text_insights = process_text_to_insight(text_request)
98
+
99
+ return {"transcription": transcription, "translation": translation, "insights": audio_text_insights}
100
  except Exception as e:
101
  # Re-raise the exception with more context to be caught by the API endpoint
102
  raise Exception(f"Error processing audio with Gemini: {str(e)}")
services/audio_whisper.py CHANGED
@@ -3,6 +3,12 @@ import torch
3
  import tempfile
4
  import os
5
  from typing import Dict
 
 
 
 
 
 
6
 
7
  # Determine the most efficient device available (CUDA if possible, otherwise CPU)
8
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
@@ -17,7 +23,7 @@ except Exception as e:
17
  print(f"Fatal: Error loading Whisper model: {e}")
18
  model = None
19
 
20
- def process_audio_with_whisper(audio_bytes: bytes) -> Dict[str, str]:
21
  """
22
  Transcribes and translates a given audio file's bytes using the Whisper model.
23
 
@@ -30,7 +36,7 @@ def process_audio_with_whisper(audio_bytes: bytes) -> Dict[str, str]:
30
 
31
  Returns:
32
  A dictionary containing the Tagalog transcription and English translation.
33
- Example: {"transcription": "...", "translation": "..."}
34
 
35
  Raises:
36
  ValueError: If the Whisper model was not loaded successfully.
@@ -67,10 +73,18 @@ def process_audio_with_whisper(audio_bytes: bytes) -> Dict[str, str]:
67
  task="translate"
68
  )
69
 
 
 
 
 
 
 
70
  return {
71
- "transcription": transcription_result.get('text', '').strip(), # type: ignore
72
- "translation": translation_result.get('text', '').strip() # type: ignore
 
73
  }
 
74
  except Exception as e:
75
  # Log and re-raise any exceptions to be handled by the FastAPI endpoint
76
  print(f"An error occurred during Whisper processing: {e}")
@@ -78,4 +92,6 @@ def process_audio_with_whisper(audio_bytes: bytes) -> Dict[str, str]:
78
  finally:
79
  # Ensure the temporary file is deleted after processing
80
  if 'temp_path' in locals() and os.path.exists(temp_path):
81
- os.remove(temp_path)
 
 
 
3
  import tempfile
4
  import os
5
  from typing import Dict
6
+ from services.text_processor import process_text_to_insight
7
+ from pydantic import BaseModel
8
+
9
+ # Add the TextRequest model definition here or import it
10
+ class TextRequest(BaseModel):
11
+ text: str
12
 
13
  # Determine the most efficient device available (CUDA if possible, otherwise CPU)
14
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
 
23
  print(f"Fatal: Error loading Whisper model: {e}")
24
  model = None
25
 
26
+ def process_audio_with_whisper(audio_bytes: bytes):
27
  """
28
  Transcribes and translates a given audio file's bytes using the Whisper model.
29
 
 
36
 
37
  Returns:
38
  A dictionary containing the Tagalog transcription and English translation.
39
+ Example: {"transcription": "...", "translation": "...", "insights": "..."}
40
 
41
  Raises:
42
  ValueError: If the Whisper model was not loaded successfully.
 
73
  task="translate"
74
  )
75
 
76
+ # Get the transcribed text
77
+ transcribed_text = transcription_result.get('text', '').strip()
78
+
79
+ insights = process_text_to_insight(transcribed_text)
80
+
81
+
82
  return {
83
+ "transcription": transcribed_text,
84
+ "translation": translation_result.get('text', '').strip(),
85
+ "insights": insights
86
  }
87
+
88
  except Exception as e:
89
  # Log and re-raise any exceptions to be handled by the FastAPI endpoint
90
  print(f"An error occurred during Whisper processing: {e}")
 
92
  finally:
93
  # Ensure the temporary file is deleted after processing
94
  if 'temp_path' in locals() and os.path.exists(temp_path):
95
+ os.remove(temp_path)
96
+
97
+ print("=== Debug Whisper Output ===")
services/text_processor.py CHANGED
@@ -10,11 +10,11 @@ from langchain.output_parsers import OutputFixingParser
10
  from langchain.prompts import PromptTemplate, FewShotPromptTemplate
11
  from datetime import datetime
12
  import time
 
13
 
14
 
15
  import os
16
  from dotenv import load_dotenv
17
- import tqdm
18
 
19
  # Load environment variables from a .env file in the root directory
20
  load_dotenv()
@@ -140,39 +140,39 @@ sentiment_examples = [{'input': 'There is an unauthorized charge on my BPI Famil
140
  # --- Pydantic Models
141
  class GeneralInfo(BaseModel):
142
  case_id: Optional[str] = Field(None, description="An unique identifier given to each case message")
143
- raw_message: str = Field(None, description="The raw and unstructured form of the original message or conversation") # type: ignore
144
- message_source: Literal['Email', 'Phone', 'Branch', 'Facebook'] = Field(None, description="The channel to which the text was received from") # type: ignore
145
  customer_tier: Optional[Literal['High', 'Mid', 'Low']] = Field(None, description="The tier of the customer sending the message")
146
  status: Optional[Literal['New', 'Assigned', 'Closed']] = Field(None, description="The status of the message, whether it was new, already assigned, or closed")
147
  start_date: Optional[datetime] = Field(None, description="The date and time when the message was initiated or received.")
148
  close_date: Optional[datetime] = Field(None, description="The date and time when the message was marked as closed or resolved.")
149
 
150
  class TextOverview(BaseModel):
151
- summary: str = Field(None, description="A one liner summary of the text provided. Indicates the main purpose and intention of the text. Use proper case.") # type: ignore
152
- tags: List[str] = Field(None, description="A list of keywords that can be used to tag and classify the message meaningfuly. Use lowercase") # type: ignore
153
 
154
  class TransactionType(BaseModel):
155
- interaction_type: Literal['Request', 'Inquiry', 'Complaint'] = Field(None, description="The interaction type of the message, indicates whether the customer is inquiring, complaining, or requesting to the bank") # type: ignore
156
- product_type: Literal['Credit Cards', 'Deposits', 'Loans'] = Field(None, description="The product that is best connected to the purpose of the message. Indicates if the message is related to Credit Cards, Deposits, or Loans") # type: ignore
157
 
158
  class SentimentConfidence(BaseModel):
159
- sentiment_tag: str = Field(None, description="The sentiment tag being assessed. Can be either 'Positivee', 'Negative', or 'Neutral") # type: ignore
160
  sentiment_confidence_score: Optional[float] = Field(None, ge=0.0, le=1.0, description="how confident the given sentiment category is when associated with the intent of the message. Use two decimal points for the score")
161
  emotional_indicators: Optional[List[str]] = Field(None, description="Bigrams or trigrams that best display the particular sentiment of the message. Use lowercase. Use 'Blank' if there is no good keyword.")
162
 
163
  class Sentiment(BaseModel):
164
- sentiment_category: Literal['Negative', 'Neutral', 'Positive'] = Field(None, description="the sentiment demonstrated within the message. Indicates whether the message has negative, positive, or neutral connotations") # type: ignore
165
  sentiment_reasoning: Optional[str] = Field(None, description="A one liner that depicts main reason why the text was categorized as a certain sentiment. No need to add any emphases on keywords. Use proper case.")
166
  sentiment_distribution: List[SentimentConfidence] = Field(description="A distribution that shows how likely each sentiment (Positive, Neutral, and Negative). Note that the sum of the confidence scores should be equal to 1.0 since it's a probability distribution")
167
 
168
  class Urgency(BaseModel):
169
- priority_category: Literal['High', 'Medium', 'Low'] = Field(None, description = "Describes how urgent a message needs to be addressed.") # type: ignore
170
  priority_reason: Optional[str] = Field(None, description = "An explanation of why the priority level of a message is the way it is.")
171
 
172
  class ChatLogEntry(BaseModel):
173
- turn_id: int = Field(None, description="A number that indicates the order in which the message is found in the conversation") # type: ignore
174
- speaker: Literal['Customer', 'Bank Agent', 'Chatbot'] = Field(None, description="The entity who sent the message during the specified turn") # type: ignore
175
- text: str = Field(None, description="The message sent within the turn of the speaker") # type: ignore
176
 
177
  class DialogueHistory(BaseModel):
178
  dialogue_history: List[ChatLogEntry] = Field(
@@ -232,7 +232,7 @@ ctt_fewshot_prompt = FewShotPromptTemplate(
232
  ctt_chain_fs = ctt_fewshot_prompt | llm_text_insights
233
 
234
  ctt_chain_wrapped = RunnableLambda(lambda x: {
235
- "text_to_classify": x["text"] # type: ignore
236
  }) | ctt_chain_fs
237
 
238
 
@@ -267,8 +267,8 @@ cpl_fewshot_prompt = FewShotPromptTemplate(
267
  cpl_chain_fs = cpl_fewshot_prompt | llm_text_insights
268
 
269
  cpl_chain_wrapped = RunnableLambda(lambda x: {
270
- "text_to_classify": x["text"] # type: ignore
271
- }) | cpl_chain_fs | RunnableLambda(lambda x: urgency_parser.parse(x.content).model_dump_json(indent=2)) # type: ignore
272
 
273
  ct_prompt = PromptTemplate.from_template(
274
  """You are an expert contact center operations agent and analyst at a banking firm. Your task is to review customer messages and classify each message by selecting exactly one label from the following services/products offered by the bank: "labels": ['Credit Cards', 'Loans', 'Deposits'].
@@ -290,7 +290,7 @@ ct_fewshot_prompt = FewShotPromptTemplate(
290
  ct_chain_fs = ct_fewshot_prompt | llm_text_insights
291
 
292
  ct_chain_wrapped = RunnableLambda(lambda x: {
293
- "text_to_classify": x["text"] # type: ignore
294
  }) | ct_chain_fs
295
 
296
  sentiment_prompt = PromptTemplate.from_template(
@@ -322,7 +322,7 @@ sentiment_fewshot_prompt = FewShotPromptTemplate(
322
 
323
  sentiment_chain_fs = sentiment_fewshot_prompt | llm_text_insights
324
 
325
- sentiment_chain_wrapped = RunnableLambda(lambda x: {"text_to_classify": x["text"]}) | sentiment_chain_fs | RunnableLambda(lambda x: sentiment_parser.parse(x.content).model_dump_json(indent=2)) # type: ignore
326
 
327
  summary_prompt = PromptTemplate.from_template(
328
  """You are an expert contact center operations agent and analyst at a banking firm.
@@ -336,7 +336,7 @@ summary_prompt = PromptTemplate.from_template(
336
  summary_chain = summary_prompt | llm_text_insights
337
 
338
  summary_chain_wrapped = RunnableLambda(lambda x: {
339
- "text_to_summarize": x["text"] # type: ignore
340
  }) | summary_chain
341
 
342
  kw_prompt = PromptTemplate.from_template(
@@ -353,7 +353,7 @@ kw_prompt = PromptTemplate.from_template(
353
  kw_chain = kw_prompt | llm_text_insights
354
 
355
  kw_chain_wrapped = RunnableLambda(lambda x: {
356
- "text_to_extract": x["text"] # type: ignore
357
  }) | kw_chain
358
 
359
 
@@ -392,35 +392,35 @@ dialogue_history_prompt = PromptTemplate(
392
  dialogue_history_chain = dialogue_history_prompt | llm_text_insights
393
 
394
  dialogue_history_chain_wrapped = RunnableLambda(lambda x: {
395
- "sample_text": x["text"] # type: ignore
396
- }) | dialogue_history_chain | RunnableLambda(lambda x: dialogue_history_parser.parse(x.content).model_dump_json(indent=2)) # type: ignore
397
 
398
 
399
  def process_text_to_insight(text, sleep_time_req = 5):
400
  try:
401
  result = {}
402
 
403
- result['case_transaction_type'] = ctt_chain_wrapped.invoke({'text': text}).content.strip() # type: ignore
404
  time.sleep(sleep_time_req)
405
 
406
  result['case_priority_level'] = cpl_chain_wrapped.invoke({'text': text})
407
  time.sleep(sleep_time_req)
408
 
409
- result['case_type'] = ct_chain_wrapped.invoke({'text': text}).content.strip() # type: ignore
410
  time.sleep(sleep_time_req)
411
 
412
  result['sentiment'] = sentiment_chain_wrapped.invoke({'text': text})
413
  time.sleep(sleep_time_req)
414
 
415
- result['summary'] = summary_chain_wrapped.invoke({'text': text}).content.strip() # type: ignore
416
  time.sleep(sleep_time_req)
417
 
418
- result['keywords'] = kw_chain_wrapped.invoke({'text': text}).content.strip() # type: ignore
419
 
420
  result['dialogue_history'] = dialogue_history_chain_wrapped.invoke({'text': text})
421
 
422
  except Exception as e:
423
- tqdm.write(f"[error] Skipping row due to: {e}") # type: ignore
424
  result = {
425
  "case_text": text,
426
  "case_transaction_type": None,
 
10
  from langchain.prompts import PromptTemplate, FewShotPromptTemplate
11
  from datetime import datetime
12
  import time
13
+ from tqdm import tqdm
14
 
15
 
16
  import os
17
  from dotenv import load_dotenv
 
18
 
19
  # Load environment variables from a .env file in the root directory
20
  load_dotenv()
 
140
  # --- Pydantic Models
141
  class GeneralInfo(BaseModel):
142
  case_id: Optional[str] = Field(None, description="An unique identifier given to each case message")
143
+ raw_message: str = Field(None, description="The raw and unstructured form of the original message or conversation")
144
+ message_source: Literal['Email', 'Phone', 'Branch', 'Facebook'] = Field(None, description="The channel to which the text was received from")
145
  customer_tier: Optional[Literal['High', 'Mid', 'Low']] = Field(None, description="The tier of the customer sending the message")
146
  status: Optional[Literal['New', 'Assigned', 'Closed']] = Field(None, description="The status of the message, whether it was new, already assigned, or closed")
147
  start_date: Optional[datetime] = Field(None, description="The date and time when the message was initiated or received.")
148
  close_date: Optional[datetime] = Field(None, description="The date and time when the message was marked as closed or resolved.")
149
 
150
  class TextOverview(BaseModel):
151
+ summary: str = Field(None, description="A one liner summary of the text provided. Indicates the main purpose and intention of the text. Use proper case.")
152
+ tags: List[str] = Field(None, description="A list of keywords that can be used to tag and classify the message meaningfuly. Use lowercase")
153
 
154
  class TransactionType(BaseModel):
155
+ interaction_type: Literal['Request', 'Inquiry', 'Complaint'] = Field(None, description="The interaction type of the message, indicates whether the customer is inquiring, complaining, or requesting to the bank")
156
+ product_type: Literal['Credit Cards', 'Deposits', 'Loans'] = Field(None, description="The product that is best connected to the purpose of the message. Indicates if the message is related to Credit Cards, Deposits, or Loans")
157
 
158
  class SentimentConfidence(BaseModel):
159
+ sentiment_tag: str = Field(None, description="The sentiment tag being assessed. Can be either 'Positivee', 'Negative', or 'Neutral")
160
  sentiment_confidence_score: Optional[float] = Field(None, ge=0.0, le=1.0, description="how confident the given sentiment category is when associated with the intent of the message. Use two decimal points for the score")
161
  emotional_indicators: Optional[List[str]] = Field(None, description="Bigrams or trigrams that best display the particular sentiment of the message. Use lowercase. Use 'Blank' if there is no good keyword.")
162
 
163
  class Sentiment(BaseModel):
164
+ sentiment_category: Literal['Negative', 'Neutral', 'Positive'] = Field(None, description="the sentiment demonstrated within the message. Indicates whether the message has negative, positive, or neutral connotations")
165
  sentiment_reasoning: Optional[str] = Field(None, description="A one liner that depicts main reason why the text was categorized as a certain sentiment. No need to add any emphases on keywords. Use proper case.")
166
  sentiment_distribution: List[SentimentConfidence] = Field(description="A distribution that shows how likely each sentiment (Positive, Neutral, and Negative). Note that the sum of the confidence scores should be equal to 1.0 since it's a probability distribution")
167
 
168
  class Urgency(BaseModel):
169
+ priority_category: Literal['High', 'Medium', 'Low'] = Field(None, description = "Describes how urgent a message needs to be addressed.")
170
  priority_reason: Optional[str] = Field(None, description = "An explanation of why the priority level of a message is the way it is.")
171
 
172
  class ChatLogEntry(BaseModel):
173
+ turn_id: int = Field(None, description="A number that indicates the order in which the message is found in the conversation")
174
+ speaker: Literal['Customer', 'Bank Agent', 'Chatbot'] = Field(None, description="The entity who sent the message during the specified turn")
175
+ text: str = Field(None, description="The message sent within the turn of the speaker")
176
 
177
  class DialogueHistory(BaseModel):
178
  dialogue_history: List[ChatLogEntry] = Field(
 
232
  ctt_chain_fs = ctt_fewshot_prompt | llm_text_insights
233
 
234
  ctt_chain_wrapped = RunnableLambda(lambda x: {
235
+ "text_to_classify": x["text"]
236
  }) | ctt_chain_fs
237
 
238
 
 
267
  cpl_chain_fs = cpl_fewshot_prompt | llm_text_insights
268
 
269
  cpl_chain_wrapped = RunnableLambda(lambda x: {
270
+ "text_to_classify": x["text"]
271
+ }) | cpl_chain_fs | RunnableLambda(lambda x: urgency_parser.parse(x.content).model_dump_json(indent=2))
272
 
273
  ct_prompt = PromptTemplate.from_template(
274
  """You are an expert contact center operations agent and analyst at a banking firm. Your task is to review customer messages and classify each message by selecting exactly one label from the following services/products offered by the bank: "labels": ['Credit Cards', 'Loans', 'Deposits'].
 
290
  ct_chain_fs = ct_fewshot_prompt | llm_text_insights
291
 
292
  ct_chain_wrapped = RunnableLambda(lambda x: {
293
+ "text_to_classify": x["text"]
294
  }) | ct_chain_fs
295
 
296
  sentiment_prompt = PromptTemplate.from_template(
 
322
 
323
  sentiment_chain_fs = sentiment_fewshot_prompt | llm_text_insights
324
 
325
+ sentiment_chain_wrapped = RunnableLambda(lambda x: {"text_to_classify": x["text"]}) | sentiment_chain_fs | RunnableLambda(lambda x: sentiment_parser.parse(x.content).model_dump_json(indent=2))
326
 
327
  summary_prompt = PromptTemplate.from_template(
328
  """You are an expert contact center operations agent and analyst at a banking firm.
 
336
  summary_chain = summary_prompt | llm_text_insights
337
 
338
  summary_chain_wrapped = RunnableLambda(lambda x: {
339
+ "text_to_summarize": x["text"]
340
  }) | summary_chain
341
 
342
  kw_prompt = PromptTemplate.from_template(
 
353
  kw_chain = kw_prompt | llm_text_insights
354
 
355
  kw_chain_wrapped = RunnableLambda(lambda x: {
356
+ "text_to_extract": x["text"]
357
  }) | kw_chain
358
 
359
 
 
392
  dialogue_history_chain = dialogue_history_prompt | llm_text_insights
393
 
394
  dialogue_history_chain_wrapped = RunnableLambda(lambda x: {
395
+ "sample_text": x["text"]
396
+ }) | dialogue_history_chain | RunnableLambda(lambda x: dialogue_history_parser.parse(x.content).model_dump_json(indent=2))
397
 
398
 
399
  def process_text_to_insight(text, sleep_time_req = 5):
400
  try:
401
  result = {}
402
 
403
+ result['case_transaction_type'] = ctt_chain_wrapped.invoke({'text': text}).content.strip()
404
  time.sleep(sleep_time_req)
405
 
406
  result['case_priority_level'] = cpl_chain_wrapped.invoke({'text': text})
407
  time.sleep(sleep_time_req)
408
 
409
+ result['case_type'] = ct_chain_wrapped.invoke({'text': text}).content.strip()
410
  time.sleep(sleep_time_req)
411
 
412
  result['sentiment'] = sentiment_chain_wrapped.invoke({'text': text})
413
  time.sleep(sleep_time_req)
414
 
415
+ result['summary'] = summary_chain_wrapped.invoke({'text': text}).content.strip()
416
  time.sleep(sleep_time_req)
417
 
418
+ result['keywords'] = kw_chain_wrapped.invoke({'text': text}).content.strip()
419
 
420
  result['dialogue_history'] = dialogue_history_chain_wrapped.invoke({'text': text})
421
 
422
  except Exception as e:
423
+ tqdm.write(f"[error] Skipping row due to: {e}")
424
  result = {
425
  "case_text": text,
426
  "case_transaction_type": None,