Fade0510 commited on
Commit
f41ca4f
·
1 Parent(s): 36f5dd4

Add ModerationAgent and update QualityScoringAgent accordingly

Browse files
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
src/agents/ModerationAgent.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_openai import ChatOpenAI
2
+ from langchain_core.prompts import PromptTemplate
3
+ from src.agents.CallState import CallState
4
+
5
+ class ModerationAgent:
6
+ """
7
+ Agent that uses LLM to detect and redact obscene words, replacing them with ***.
8
+ Updates the clean_content in the state.
9
+ """
10
+
11
+ def __init__(self):
12
+ self.llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
13
+
14
+ def __call__(self, state: CallState) -> CallState:
15
+ text = state.get("clean_content") or state.get("content") or ""
16
+ if not text:
17
+ return state
18
+
19
+ prompt = PromptTemplate.from_template(
20
+ "You are a moderation assistant. Review the following text. "
21
+ "If you find any obscene words, profanity, or inappropriate language, replace them entirely with ***. "
22
+ "Do not change any other words. Return only the redacted text.\n\n"
23
+ "Text:\n{text}"
24
+ )
25
+
26
+ chain = prompt | self.llm
27
+ result = chain.invoke({"text": text})
28
+
29
+ # Save the redacted text back to clean_content
30
+ state["clean_content"] = result.content
31
+ return state
src/agents/QualityScoringAgent.py CHANGED
@@ -25,6 +25,13 @@ class QualityScoringAgent:
25
 
26
  try:
27
  result_json = json.loads(result_text)
 
 
 
 
 
 
 
28
  state["quality_scores"] = result_json
29
  except json.JSONDecodeError:
30
  state["quality_scores"] = {"raw_evaluation": result_text}
 
25
 
26
  try:
27
  result_json = json.loads(result_text)
28
+ profanity_count = clean_text.count("***")
29
+ if profanity_count > 0:
30
+ result_json["profanity"] = profanity_count
31
+ for key in ["tone", "professionalism", "structured_resolution"]:
32
+ if key in result_json and isinstance(result_json[key], (int, float)):
33
+ result_json[key] = max(0, result_json[key] - 3)
34
+
35
  state["quality_scores"] = result_json
36
  except json.JSONDecodeError:
37
  state["quality_scores"] = {"raw_evaluation": result_text}
src/agents/__init__.py CHANGED
@@ -3,4 +3,5 @@ from .IntakeAgent import IntakeAgent
3
  from .TranscriptionAgent import TranscriptionAgent
4
  from .SummarizationAgent import SummarizationAgent
5
  from .QualityScoringAgent import QualityScoringAgent
 
6
  from .Router import Router
 
3
  from .TranscriptionAgent import TranscriptionAgent
4
  from .SummarizationAgent import SummarizationAgent
5
  from .QualityScoringAgent import QualityScoringAgent
6
+ from .ModerationAgent import ModerationAgent
7
  from .Router import Router
src/streamlit_app.py CHANGED
@@ -12,17 +12,18 @@ sys.path.append(os.path.dirname(os.path.dirname(__file__)))
12
 
13
  PROCESSED_DIR = "data/processed_results"
14
 
 
15
  def apply_material_css():
16
  st.markdown("""
17
  <style>
18
  /* Material Design CSS Overrides */
19
-
20
  .stApp {
21
  font-family: 'Roboto', 'Inter', sans-serif;
22
  background-color: #121212;
23
  color: #FFFFFF;
24
  }
25
-
26
  /* Material Cards for metrics and sections */
27
  .material-card {
28
  background-color: #1E1E1E;
@@ -31,11 +32,11 @@ def apply_material_css():
31
  box-shadow: 0 4px 6px rgba(0,0,0,0.3);
32
  margin-bottom: 20px;
33
  }
34
-
35
  h1, h2, h3, h4 {
36
  font-weight: 500;
37
  }
38
-
39
  /* Subtle styling for Streamlit columns to look like cards */
40
  [data-testid="column"] {
41
  background-color: #1E1E1E;
@@ -43,18 +44,19 @@ def apply_material_css():
43
  padding: 20px;
44
  box-shadow: 0 4px 6px rgba(0,0,0,0.3);
45
  }
46
-
47
  </style>
48
  """, unsafe_allow_html=True)
49
 
 
50
  def display_results(final_state):
51
  st.subheader("Workflow Results")
52
-
53
  col1, col2 = st.columns(2)
54
  with col1:
55
  st.markdown("### Summary")
56
  st.write(final_state.get("summary", "No summary generated."))
57
-
58
  st.markdown("### Key Points")
59
  key_points = final_state.get("key_points", "No key points generated.")
60
  if isinstance(key_points, list):
@@ -62,7 +64,7 @@ def display_results(final_state):
62
  st.markdown(f"- {point}")
63
  else:
64
  st.write(key_points)
65
-
66
  with col2:
67
  st.markdown("### Quality Scores")
68
  quality_scores = final_state.get("quality_scores", {})
@@ -90,7 +92,7 @@ def display_results(final_state):
90
  ))
91
  # Adjust colors for dark theme
92
  fig.update_layout(
93
- height=180,
94
  margin=dict(l=20, r=20, t=40, b=20),
95
  paper_bgcolor='#1E1E1E',
96
  plot_bgcolor='#1E1E1E',
@@ -99,12 +101,12 @@ def display_results(final_state):
99
  st.plotly_chart(fig, use_container_width=True)
100
  except (ValueError, TypeError):
101
  st.write(f"**{metric.replace('_', ' ').title()}**: {quality_scores[metric]}")
102
-
103
  if "notes" in quality_scores:
104
  st.write("**Notes:**", quality_scores["notes"])
105
  else:
106
  st.write("No specific scores generated.", quality_scores)
107
-
108
  st.markdown("### Metadata")
109
  metadata = final_state.get("metadata", {})
110
  if isinstance(metadata, dict) and metadata:
@@ -113,15 +115,16 @@ def display_results(final_state):
113
  else:
114
  st.write("No metadata available.")
115
 
 
116
  def main():
117
  st.set_page_config(page_title="Call Center Data Analysis", layout="wide")
118
  apply_material_css()
119
-
120
  st.title("Call Center Data Analysis Dashboard")
121
-
122
  os.makedirs(PROCESSED_DIR, exist_ok=True)
123
  os.makedirs("tmp", exist_ok=True)
124
-
125
  if "view_mode" not in st.session_state:
126
  st.session_state.view_mode = "none"
127
 
@@ -133,22 +136,27 @@ def main():
133
 
134
  st.sidebar.header("Upload New File")
135
  uploaded_file = st.sidebar.file_uploader(
136
- "Upload a file",
137
  type=["json", "mp3", "wav", "csv"],
138
  on_change=set_upload_mode
139
  )
140
-
141
  st.sidebar.markdown("---")
142
  st.sidebar.header("Processed Files")
143
-
144
  # Get list of processed files
145
  processed_files = [f for f in os.listdir(PROCESSED_DIR) if f.endswith(".json")]
146
- processed_files.sort(reverse=True) # Show newest (or reverse alphabetical) first
147
-
148
  selected_file = None
149
  if processed_files:
150
  options = ["-- Select a file --"] + processed_files
151
- selected_dropdown = st.sidebar.selectbox("View cached results:", options, on_change=set_dropdown_mode)
 
 
 
 
 
152
  if selected_dropdown != "-- Select a file --":
153
  selected_file = selected_dropdown
154
  else:
@@ -157,18 +165,18 @@ def main():
157
  # Prioritize based on view_mode
158
  if st.session_state.view_mode == "upload" and uploaded_file is not None:
159
  st.success(f"File '{uploaded_file.name}' uploaded successfully!")
160
-
161
  from src.workflow import build_workflow
162
-
163
  file_path = os.path.join("tmp", uploaded_file.name)
164
  with open(file_path, "wb") as f:
165
  f.write(uploaded_file.getbuffer())
166
-
167
  file_extension = uploaded_file.name.split('.')[-1].lower()
168
-
169
  # Check if already processed to avoid reprocessing on rerun if same file is in uploader
170
  cached_json_path = os.path.join(PROCESSED_DIR, f"{uploaded_file.name}.json")
171
-
172
  if os.path.exists(cached_json_path):
173
  st.info("Loading cached results for this file...")
174
  with open(cached_json_path, 'r') as f:
@@ -182,35 +190,38 @@ def main():
182
  "file_path": file_path,
183
  "file_type": file_extension
184
  }
185
-
186
  final_state = workflow.invoke(initial_state)
187
-
188
  # Cache the results
189
  with open(cached_json_path, 'w') as f:
190
  json.dump(final_state, f)
191
-
 
 
192
  display_results(final_state)
193
-
194
- elif (st.session_state.view_mode == "dropdown" or st.session_state.view_mode == "none") and selected_file is not None:
 
195
  st.info(f"Loading cached results for '{selected_file.replace('.json', '')}'")
196
  cached_json_path = os.path.join(PROCESSED_DIR, selected_file)
197
  with open(cached_json_path, 'r') as f:
198
  final_state = json.load(f)
199
  display_results(final_state)
200
-
201
- elif uploaded_file is not None:
202
  st.success(f"File '{uploaded_file.name}' uploaded successfully!")
203
-
204
  from src.workflow import build_workflow
205
-
206
  file_path = os.path.join("tmp", uploaded_file.name)
207
  with open(file_path, "wb") as f:
208
  f.write(uploaded_file.getbuffer())
209
-
210
  file_extension = uploaded_file.name.split('.')[-1].lower()
211
-
212
  cached_json_path = os.path.join(PROCESSED_DIR, f"{uploaded_file.name}.json")
213
-
214
  if os.path.exists(cached_json_path):
215
  st.info("Loading cached results for this file...")
216
  with open(cached_json_path, 'r') as f:
@@ -227,11 +238,12 @@ def main():
227
  final_state = workflow.invoke(initial_state)
228
  with open(cached_json_path, 'w') as f:
229
  json.dump(final_state, f)
 
230
  display_results(final_state)
231
-
232
  else:
233
  st.info("Please upload a file or select a previously processed file.")
234
 
 
235
  if __name__ == "__main__":
236
  main()
237
-
 
12
 
13
  PROCESSED_DIR = "data/processed_results"
14
 
15
+
16
  def apply_material_css():
17
  st.markdown("""
18
  <style>
19
  /* Material Design CSS Overrides */
20
+
21
  .stApp {
22
  font-family: 'Roboto', 'Inter', sans-serif;
23
  background-color: #121212;
24
  color: #FFFFFF;
25
  }
26
+
27
  /* Material Cards for metrics and sections */
28
  .material-card {
29
  background-color: #1E1E1E;
 
32
  box-shadow: 0 4px 6px rgba(0,0,0,0.3);
33
  margin-bottom: 20px;
34
  }
35
+
36
  h1, h2, h3, h4 {
37
  font-weight: 500;
38
  }
39
+
40
  /* Subtle styling for Streamlit columns to look like cards */
41
  [data-testid="column"] {
42
  background-color: #1E1E1E;
 
44
  padding: 20px;
45
  box-shadow: 0 4px 6px rgba(0,0,0,0.3);
46
  }
47
+
48
  </style>
49
  """, unsafe_allow_html=True)
50
 
51
+
52
  def display_results(final_state):
53
  st.subheader("Workflow Results")
54
+
55
  col1, col2 = st.columns(2)
56
  with col1:
57
  st.markdown("### Summary")
58
  st.write(final_state.get("summary", "No summary generated."))
59
+
60
  st.markdown("### Key Points")
61
  key_points = final_state.get("key_points", "No key points generated.")
62
  if isinstance(key_points, list):
 
64
  st.markdown(f"- {point}")
65
  else:
66
  st.write(key_points)
67
+
68
  with col2:
69
  st.markdown("### Quality Scores")
70
  quality_scores = final_state.get("quality_scores", {})
 
92
  ))
93
  # Adjust colors for dark theme
94
  fig.update_layout(
95
+ height=180,
96
  margin=dict(l=20, r=20, t=40, b=20),
97
  paper_bgcolor='#1E1E1E',
98
  plot_bgcolor='#1E1E1E',
 
101
  st.plotly_chart(fig, use_container_width=True)
102
  except (ValueError, TypeError):
103
  st.write(f"**{metric.replace('_', ' ').title()}**: {quality_scores[metric]}")
104
+
105
  if "notes" in quality_scores:
106
  st.write("**Notes:**", quality_scores["notes"])
107
  else:
108
  st.write("No specific scores generated.", quality_scores)
109
+
110
  st.markdown("### Metadata")
111
  metadata = final_state.get("metadata", {})
112
  if isinstance(metadata, dict) and metadata:
 
115
  else:
116
  st.write("No metadata available.")
117
 
118
+
119
  def main():
120
  st.set_page_config(page_title="Call Center Data Analysis", layout="wide")
121
  apply_material_css()
122
+
123
  st.title("Call Center Data Analysis Dashboard")
124
+
125
  os.makedirs(PROCESSED_DIR, exist_ok=True)
126
  os.makedirs("tmp", exist_ok=True)
127
+
128
  if "view_mode" not in st.session_state:
129
  st.session_state.view_mode = "none"
130
 
 
136
 
137
  st.sidebar.header("Upload New File")
138
  uploaded_file = st.sidebar.file_uploader(
139
+ "Upload a file",
140
  type=["json", "mp3", "wav", "csv"],
141
  on_change=set_upload_mode
142
  )
143
+
144
  st.sidebar.markdown("---")
145
  st.sidebar.header("Processed Files")
146
+
147
  # Get list of processed files
148
  processed_files = [f for f in os.listdir(PROCESSED_DIR) if f.endswith(".json")]
149
+ processed_files.sort(reverse=True) # Show newest (or reverse alphabetical) first
150
+
151
  selected_file = None
152
  if processed_files:
153
  options = ["-- Select a file --"] + processed_files
154
+ selected_dropdown = st.sidebar.selectbox(
155
+ "View cached results:",
156
+ options,
157
+ format_func=lambda x: x.replace(".json", "") if x != "-- Select a file --" else x,
158
+ on_change=set_dropdown_mode
159
+ )
160
  if selected_dropdown != "-- Select a file --":
161
  selected_file = selected_dropdown
162
  else:
 
165
  # Prioritize based on view_mode
166
  if st.session_state.view_mode == "upload" and uploaded_file is not None:
167
  st.success(f"File '{uploaded_file.name}' uploaded successfully!")
168
+
169
  from src.workflow import build_workflow
170
+
171
  file_path = os.path.join("tmp", uploaded_file.name)
172
  with open(file_path, "wb") as f:
173
  f.write(uploaded_file.getbuffer())
174
+
175
  file_extension = uploaded_file.name.split('.')[-1].lower()
176
+
177
  # Check if already processed to avoid reprocessing on rerun if same file is in uploader
178
  cached_json_path = os.path.join(PROCESSED_DIR, f"{uploaded_file.name}.json")
179
+
180
  if os.path.exists(cached_json_path):
181
  st.info("Loading cached results for this file...")
182
  with open(cached_json_path, 'r') as f:
 
190
  "file_path": file_path,
191
  "file_type": file_extension
192
  }
193
+
194
  final_state = workflow.invoke(initial_state)
195
+
196
  # Cache the results
197
  with open(cached_json_path, 'w') as f:
198
  json.dump(final_state, f)
199
+
200
+ st.rerun()
201
+
202
  display_results(final_state)
203
+
204
+ elif (
205
+ st.session_state.view_mode == "dropdown" or st.session_state.view_mode == "none") and selected_file is not None:
206
  st.info(f"Loading cached results for '{selected_file.replace('.json', '')}'")
207
  cached_json_path = os.path.join(PROCESSED_DIR, selected_file)
208
  with open(cached_json_path, 'r') as f:
209
  final_state = json.load(f)
210
  display_results(final_state)
211
+
212
+ elif st.session_state.view_mode != "dropdown" and uploaded_file is not None:
213
  st.success(f"File '{uploaded_file.name}' uploaded successfully!")
214
+
215
  from src.workflow import build_workflow
216
+
217
  file_path = os.path.join("tmp", uploaded_file.name)
218
  with open(file_path, "wb") as f:
219
  f.write(uploaded_file.getbuffer())
220
+
221
  file_extension = uploaded_file.name.split('.')[-1].lower()
222
+
223
  cached_json_path = os.path.join(PROCESSED_DIR, f"{uploaded_file.name}.json")
224
+
225
  if os.path.exists(cached_json_path):
226
  st.info("Loading cached results for this file...")
227
  with open(cached_json_path, 'r') as f:
 
238
  final_state = workflow.invoke(initial_state)
239
  with open(cached_json_path, 'w') as f:
240
  json.dump(final_state, f)
241
+ st.rerun()
242
  display_results(final_state)
243
+
244
  else:
245
  st.info("Please upload a file or select a previously processed file.")
246
 
247
+
248
  if __name__ == "__main__":
249
  main()
 
src/workflow.py CHANGED
@@ -5,6 +5,7 @@ from src.agents import (
5
  TranscriptionAgent,
6
  SummarizationAgent,
7
  QualityScoringAgent,
 
8
  Router
9
  )
10
 
@@ -14,6 +15,7 @@ def build_workflow():
14
  # Initialize agents
15
  intake_agent = IntakeAgent()
16
  transcription_agent = TranscriptionAgent()
 
17
  summarization_agent = SummarizationAgent()
18
  quality_scoring_agent = QualityScoringAgent()
19
  router = Router()
@@ -21,6 +23,7 @@ def build_workflow():
21
  # Add nodes
22
  workflow.add_node("intake", intake_agent)
23
  workflow.add_node("transcribe", transcription_agent)
 
24
  workflow.add_node("summarize", summarization_agent)
25
  workflow.add_node("score", quality_scoring_agent)
26
 
@@ -33,12 +36,13 @@ def build_workflow():
33
  router,
34
  {
35
  "transcribe": "transcribe",
36
- "summarize": "summarize"
37
  }
38
  )
39
 
40
  # Add standard edges
41
- workflow.add_edge("transcribe", "summarize")
 
42
  workflow.add_edge("summarize", "score")
43
  workflow.add_edge("score", END)
44
 
 
5
  TranscriptionAgent,
6
  SummarizationAgent,
7
  QualityScoringAgent,
8
+ ModerationAgent,
9
  Router
10
  )
11
 
 
15
  # Initialize agents
16
  intake_agent = IntakeAgent()
17
  transcription_agent = TranscriptionAgent()
18
+ moderation_agent = ModerationAgent()
19
  summarization_agent = SummarizationAgent()
20
  quality_scoring_agent = QualityScoringAgent()
21
  router = Router()
 
23
  # Add nodes
24
  workflow.add_node("intake", intake_agent)
25
  workflow.add_node("transcribe", transcription_agent)
26
+ workflow.add_node("moderate", moderation_agent)
27
  workflow.add_node("summarize", summarization_agent)
28
  workflow.add_node("score", quality_scoring_agent)
29
 
 
36
  router,
37
  {
38
  "transcribe": "transcribe",
39
+ "summarize": "moderate"
40
  }
41
  )
42
 
43
  # Add standard edges
44
+ workflow.add_edge("transcribe", "moderate")
45
+ workflow.add_edge("moderate", "summarize")
46
  workflow.add_edge("summarize", "score")
47
  workflow.add_edge("score", END)
48