asdfasdfqrqwer commited on
Commit
41971b5
·
1 Parent(s): 4b9f9bd

Update to pixeltable 0.7.8 / gradio 5.49: UDFs to udfs.py, model_kwargs, pinned deps, python 3.11

Browse files
Files changed (4) hide show
  1. README.md +5 -1
  2. app.py +70 -77
  3. requirements.txt +6 -4
  4. udfs.py +23 -0
README.md CHANGED
@@ -4,11 +4,15 @@ emoji: 🐠
4
  colorFrom: green
5
  colorTo: purple
6
  sdk: gradio
7
- sdk_version: 5.1.0
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
  short_description: Conversation Intelligence
12
  ---
13
 
 
 
 
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
4
  colorFrom: green
5
  colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.49.0
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
  short_description: Conversation Intelligence
12
  ---
13
 
14
+ Call analysis tool built with [Pixeltable](https://github.com/pixeltable/pixeltable). Docs: https://docs.pixeltable.com
15
+
16
+ Requires an `OPENAI_API_KEY` secret.
17
+
18
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py CHANGED
@@ -1,86 +1,81 @@
 
 
1
  import gradio as gr
2
  import pixeltable as pxt
3
- from pixeltable.iterators import FrameIterator, StringSplitter
4
- from pixeltable.functions.video import extract_audio
5
- from pixeltable.functions.audio import get_metadata
6
  from pixeltable.functions import openai
7
- import os
8
- import getpass
9
- import numpy as np
10
  from pixeltable.functions.huggingface import sentence_transformer
 
 
 
 
11
 
12
- # Store OpenAI API Key
13
- if 'OPENAI_API_KEY' not in os.environ:
14
- os.environ['OPENAI_API_KEY'] = getpass.getpass('Enter your OpenAI API key:')
15
 
16
  MAX_VIDEO_SIZE_MB = 35
17
 
18
  def process_video(video_file, progress=gr.Progress()):
19
 
20
  progress(0, desc="Initializing...")
21
-
 
 
 
22
  try:
23
  # Create a Table, a View, and Computed Columns
24
- pxt.drop_dir('gong_demo', force=True)
25
  pxt.create_dir('gong_demo')
26
-
27
  calls_table = pxt.create_table(
28
  'gong_demo.calls', {
29
- "video": pxt.VideoType(nullable=True),
30
  }
31
  )
32
-
33
  # Create computed columns to store transformations and persist outputs
34
- calls_table['audio'] = extract_audio(calls_table.video, format='mp3')
35
- calls_table['metadata'] = get_metadata(calls_table.audio)
36
- calls_table['transcription'] = openai.transcriptions(audio=calls_table.audio, model='whisper-1')
37
- calls_table['transcription_text'] = calls_table.transcription.text.astype(pxt.StringType())
38
 
39
  sentences_view = pxt.create_view(
40
  'gong_demo.sentences',
41
  calls_table,
42
- iterator=StringSplitter.create(
43
  text=calls_table.transcription_text,
44
  separators='sentence'
45
  )
46
  )
47
 
48
- @pxt.expr_udf
49
- def e5_embed(text: str) -> np.ndarray:
50
- return sentence_transformer(text, model_id='intfloat/e5-large-v2')
51
-
52
- sentences_view.add_embedding_index('text', string_embed=e5_embed)
53
 
54
  progress(0.2, desc="Creating UDFs...")
55
 
56
- # Custom User-Defined Function (UDF) for Generating Insights
57
- @pxt.udf
58
- def generate_insights(transcription: str) -> list[dict]:
59
- system_msg = 'You are an AI assistant that analyzes call transcriptions. Analyze the following call transcription and provide insights on: 1. Main topics discussed 2. Action items 3. Sentiment analysis 4. Key questions asked'
60
- user_msg = f'Transcription: "{transcription}"'
61
- return [
62
- {'role': 'system', 'content': system_msg},
63
- {'role': 'user', 'content': user_msg}
64
- ]
65
-
66
  # Apply the UDF to create a new column
67
- calls_table['insights_prompt'] = generate_insights(calls_table.transcription_text)
68
-
69
  progress(0.4, desc="Generating insights...")
70
-
71
  # Generate insights using OpenAI's chat completion API
72
- calls_table['insights_response'] = openai.chat_completions(messages=calls_table.insights_prompt, model='gpt-3.5-turbo', max_tokens=500)
73
-
 
 
 
 
74
  # Extract the content of the response
75
- calls_table['insights'] = calls_table.insights_response.choices[0].message.content
76
-
77
  if not video_file:
78
- return "Please upload a video file.", ""
79
 
80
  # Check video file size
81
  video_size = os.path.getsize(video_file) / (1024 * 1024) # Convert to MB
82
  if video_size > MAX_VIDEO_SIZE_MB:
83
- return f"The video file is larger than {MAX_VIDEO_SIZE_MB} MB. Please upload a smaller file.", ""
84
 
85
  progress(0.6, desc="Processing video...")
86
 
@@ -101,71 +96,64 @@ def process_video(video_file, progress=gr.Progress()):
101
  return transcription, insights, audio, "Processing complete"
102
 
103
  except Exception as e:
104
- return f"An error occurred during video processing: {str(e)}", ""
105
 
106
  # Perform similarity search
107
  def similarity_search(query, num_results, progress=gr.Progress()):
108
  sentences_view = pxt.get_table('gong_demo.sentences')
109
-
110
  progress(0.5, desc="Performing search...")
111
 
112
- sim = sentences_view.text.similarity(query)
113
- results = sentences_view.order_by(sim, asc=False).limit(num_results).select(sentences_view.text).collect().to_pandas()
114
 
115
  progress(1.0, desc="Search complete")
116
  return results
117
 
118
  def chatbot_response(message, chat_history):
119
- @pxt.udf
120
- def create_chatbot_prompt(context: str, question: str) -> list[dict]:
121
- system_message = "You are an AI assistant that answers questions about a call based on the provided context. If the answer cannot be found in the context, say that you don't know."
122
- user_message = f"Context:\n{context}\n\nQuestion: {question}"
123
- return [
124
- {"role": "system", "content": system_message},
125
- {"role": "user", "content": user_message}
126
- ]
127
-
128
  try:
129
  sentences_view = pxt.get_table('gong_demo.sentences')
130
 
131
  # Perform similarity search to get relevant context
132
- sim = sentences_view.text.similarity(message)
133
  context = sentences_view.order_by(sim, asc=False).limit(5).select(sentences_view.text, sim=sim).collect()
134
-
135
  # Prepare the context for the prompt
136
- context_text = "\n".join([row['text'] for row in context])
137
 
138
  # Create a temporary table for the chatbot interaction
139
- temp_table = pxt.create_table('gong_demo.temp_chatbot', {'question': pxt.StringType()})
140
  temp_table.insert([{'question': message}])
141
 
142
  # Create computed columns for the prompt and response
143
- temp_table['chatbot_prompt'] = create_chatbot_prompt(context_text, temp_table.question)
144
- temp_table['chatbot_response'] = openai.chat_completions(
145
  messages=temp_table.chatbot_prompt,
146
- model='gpt-4o-mini-2024-07-18',
147
- max_tokens=300
148
- )
149
- temp_table['answer'] = temp_table.chatbot_response.choices[0].message.content
150
-
151
  answer = temp_table.select(temp_table.answer).collect()['answer'][0]
152
-
153
  # Clean up the temporary table
154
  pxt.drop_table('gong_demo.temp_chatbot', force=True)
155
-
156
- chat_history.append((message, answer))
157
- return "", chat_history # Return both expected outputs
 
158
  except Exception as e:
159
- error_message = f"An error occurred: {str(e)}"
160
- chat_history.append((message, error_message))
161
- return "", chat_history # Return both expec
 
162
 
163
  # Gradio interface
164
  with gr.Blocks(theme=gr.themes.Base()) as demo:
165
  gr.Markdown(
166
  """
167
  <div style="text-align: left; margin-bottom: 20px;">
168
- <img src="https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/source/data/pixeltable-logo-large.png" alt="Pixeltable" style="max-width: 150px;" />
169
  <h1 style="margin-top: 10px;">Call Analysis AI Tool</h1>
170
  </div>
171
  """
@@ -177,6 +165,11 @@ with gr.Blocks(theme=gr.themes.Base()) as demo:
177
  </p>
178
  """
179
  )
 
 
 
 
 
180
 
181
  with gr.Row():
182
  with gr.Column():
@@ -231,7 +224,7 @@ with gr.Blocks(theme=gr.themes.Base()) as demo:
231
  )
232
 
233
  with gr.TabItem("🤖 AI Assistant"):
234
- chatbot = gr.Chatbot(height=400, label="Chat with AI about the call")
235
  with gr.Row():
236
  msg = gr.Textbox(label="Ask a question about the call", placeholder="e.g., What were the main points discussed?", scale=4)
237
  send_btn = gr.Button("Send", variant="secondary", scale=1)
@@ -263,7 +256,7 @@ with gr.Blocks(theme=gr.themes.Base()) as demo:
263
 
264
  msg.submit(chatbot_response, [msg, chatbot], [msg, chatbot])
265
  send_btn.click(chatbot_response, [msg, chatbot], [msg, chatbot])
266
- clear.click(lambda: None, None, chatbot, queue=False)
267
 
268
  if __name__ == "__main__":
269
  demo.launch(show_api=False, allowed_paths=[os.path.expanduser("~/.pixeltable/media")])
 
1
+ import os
2
+
3
  import gradio as gr
4
  import pixeltable as pxt
 
 
 
5
  from pixeltable.functions import openai
6
+ from pixeltable.functions.audio import get_metadata
 
 
7
  from pixeltable.functions.huggingface import sentence_transformer
8
+ from pixeltable.functions.string import string_splitter
9
+ from pixeltable.functions.video import extract_audio
10
+
11
+ from udfs import create_chatbot_prompt, generate_insights
12
 
13
+ # The OpenAI API key must be provided via the OPENAI_API_KEY environment variable (a Space secret).
14
+ OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY')
 
15
 
16
  MAX_VIDEO_SIZE_MB = 35
17
 
18
  def process_video(video_file, progress=gr.Progress()):
19
 
20
  progress(0, desc="Initializing...")
21
+
22
+ if not OPENAI_API_KEY:
23
+ return '', '', None, 'OPENAI_API_KEY is not set. Add it as a Space secret and restart the Space.'
24
+
25
  try:
26
  # Create a Table, a View, and Computed Columns
27
+ pxt.drop_dir('gong_demo', force=True, if_not_exists='ignore')
28
  pxt.create_dir('gong_demo')
29
+
30
  calls_table = pxt.create_table(
31
  'gong_demo.calls', {
32
+ 'video': pxt.Video | None,
33
  }
34
  )
35
+
36
  # Create computed columns to store transformations and persist outputs
37
+ calls_table.add_computed_column(audio=extract_audio(calls_table.video, format='mp3'))
38
+ calls_table.add_computed_column(metadata=get_metadata(calls_table.audio))
39
+ calls_table.add_computed_column(transcription=openai.transcriptions(audio=calls_table.audio, model='whisper-1'))
40
+ calls_table.add_computed_column(transcription_text=calls_table.transcription.text.astype(pxt.String))
41
 
42
  sentences_view = pxt.create_view(
43
  'gong_demo.sentences',
44
  calls_table,
45
+ iterator=string_splitter(
46
  text=calls_table.transcription_text,
47
  separators='sentence'
48
  )
49
  )
50
 
51
+ sentences_view.add_embedding_index(
52
+ 'text', string_embed=sentence_transformer.using(model_id='intfloat/e5-large-v2')
53
+ )
 
 
54
 
55
  progress(0.2, desc="Creating UDFs...")
56
 
 
 
 
 
 
 
 
 
 
 
57
  # Apply the UDF to create a new column
58
+ calls_table.add_computed_column(insights_prompt=generate_insights(calls_table.transcription_text))
59
+
60
  progress(0.4, desc="Generating insights...")
61
+
62
  # Generate insights using OpenAI's chat completion API
63
+ calls_table.add_computed_column(
64
+ insights_response=openai.chat_completions(
65
+ messages=calls_table.insights_prompt, model='gpt-4o-mini', model_kwargs={'max_tokens': 500}
66
+ )
67
+ )
68
+
69
  # Extract the content of the response
70
+ calls_table.add_computed_column(insights=calls_table.insights_response.choices[0].message.content)
71
+
72
  if not video_file:
73
+ return '', '', None, 'Please upload a video file.'
74
 
75
  # Check video file size
76
  video_size = os.path.getsize(video_file) / (1024 * 1024) # Convert to MB
77
  if video_size > MAX_VIDEO_SIZE_MB:
78
+ return '', '', None, f'The video file is larger than {MAX_VIDEO_SIZE_MB} MB. Please upload a smaller file.'
79
 
80
  progress(0.6, desc="Processing video...")
81
 
 
96
  return transcription, insights, audio, "Processing complete"
97
 
98
  except Exception as e:
99
+ return '', '', None, f'An error occurred during video processing: {str(e)}'
100
 
101
  # Perform similarity search
102
  def similarity_search(query, num_results, progress=gr.Progress()):
103
  sentences_view = pxt.get_table('gong_demo.sentences')
104
+
105
  progress(0.5, desc="Performing search...")
106
 
107
+ sim = sentences_view.text.similarity(string=query)
108
+ results = sentences_view.order_by(sim, asc=False).limit(int(num_results)).select(sentences_view.text).collect().to_pandas()
109
 
110
  progress(1.0, desc="Search complete")
111
  return results
112
 
113
  def chatbot_response(message, chat_history):
 
 
 
 
 
 
 
 
 
114
  try:
115
  sentences_view = pxt.get_table('gong_demo.sentences')
116
 
117
  # Perform similarity search to get relevant context
118
+ sim = sentences_view.text.similarity(string=message)
119
  context = sentences_view.order_by(sim, asc=False).limit(5).select(sentences_view.text, sim=sim).collect()
120
+
121
  # Prepare the context for the prompt
122
+ context_text = '\n'.join([row['text'] for row in context])
123
 
124
  # Create a temporary table for the chatbot interaction
125
+ temp_table = pxt.create_table('gong_demo.temp_chatbot', {'question': pxt.String})
126
  temp_table.insert([{'question': message}])
127
 
128
  # Create computed columns for the prompt and response
129
+ temp_table.add_computed_column(chatbot_prompt=create_chatbot_prompt(context_text, temp_table.question))
130
+ temp_table.add_computed_column(chatbot_response=openai.chat_completions(
131
  messages=temp_table.chatbot_prompt,
132
+ model='gpt-4o-mini',
133
+ model_kwargs={'max_tokens': 300}
134
+ ))
135
+ temp_table.add_computed_column(answer=temp_table.chatbot_response.choices[0].message.content)
136
+
137
  answer = temp_table.select(temp_table.answer).collect()['answer'][0]
138
+
139
  # Clean up the temporary table
140
  pxt.drop_table('gong_demo.temp_chatbot', force=True)
141
+
142
+ chat_history.append({'role': 'user', 'content': message})
143
+ chat_history.append({'role': 'assistant', 'content': answer})
144
+ return '', chat_history # Return both expected outputs
145
  except Exception as e:
146
+ error_message = f'An error occurred: {str(e)}'
147
+ chat_history.append({'role': 'user', 'content': message})
148
+ chat_history.append({'role': 'assistant', 'content': error_message})
149
+ return '', chat_history # Return both expected outputs
150
 
151
  # Gradio interface
152
  with gr.Blocks(theme=gr.themes.Base()) as demo:
153
  gr.Markdown(
154
  """
155
  <div style="text-align: left; margin-bottom: 20px;">
156
+ <img src="https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/resources/pixeltable-logo-large.png" alt="Pixeltable" style="max-width: 150px;" />
157
  <h1 style="margin-top: 10px;">Call Analysis AI Tool</h1>
158
  </div>
159
  """
 
165
  </p>
166
  """
167
  )
168
+ if not OPENAI_API_KEY:
169
+ gr.Markdown(
170
+ '**`OPENAI_API_KEY` is not set.** Add it as a Space secret (Settings > Secrets) '
171
+ 'and restart the Space to enable analysis, search, and the AI assistant.'
172
+ )
173
 
174
  with gr.Row():
175
  with gr.Column():
 
224
  )
225
 
226
  with gr.TabItem("🤖 AI Assistant"):
227
+ chatbot = gr.Chatbot(height=400, label="Chat with AI about the call", type='messages')
228
  with gr.Row():
229
  msg = gr.Textbox(label="Ask a question about the call", placeholder="e.g., What were the main points discussed?", scale=4)
230
  send_btn = gr.Button("Send", variant="secondary", scale=1)
 
256
 
257
  msg.submit(chatbot_response, [msg, chatbot], [msg, chatbot])
258
  send_btn.click(chatbot_response, [msg, chatbot], [msg, chatbot])
259
+ clear.click(lambda: [], None, chatbot, queue=False)
260
 
261
  if __name__ == "__main__":
262
  demo.launch(show_api=False, allowed_paths=[os.path.expanduser("~/.pixeltable/media")])
requirements.txt CHANGED
@@ -1,4 +1,6 @@
1
- pixeltable
2
- openai
3
- sentence-transformers
4
- spacy
 
 
 
1
+ pixeltable==0.7.8
2
+ openai==3.16.2
3
+ sentence-transformers==6.1.0
4
+ spacy==3.8.16
5
+ en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl
6
+ numpy==2.4.6
udfs.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pixeltable as pxt
2
+
3
+
4
+ # Pixeltable rejects @pxt.udf defined in the __main__ script or nested inside
5
+ # functions; HF Spaces runs `python app.py`, so UDFs live in this module.
6
+ @pxt.udf
7
+ def generate_insights(transcription: str) -> list[dict]:
8
+ system_msg = 'You are an AI assistant that analyzes call transcriptions. Analyze the following call transcription and provide insights on: 1. Main topics discussed 2. Action items 3. Sentiment analysis 4. Key questions asked'
9
+ user_msg = f'Transcription: "{transcription}"'
10
+ return [
11
+ {'role': 'system', 'content': system_msg},
12
+ {'role': 'user', 'content': user_msg},
13
+ ]
14
+
15
+
16
+ @pxt.udf
17
+ def create_chatbot_prompt(context: str, question: str) -> list[dict]:
18
+ system_message = "You are an AI assistant that answers questions about a call based on the provided context. If the answer cannot be found in the context, say that you don't know."
19
+ user_message = f'Context:\n{context}\n\nQuestion: {question}'
20
+ return [
21
+ {'role': 'system', 'content': system_message},
22
+ {'role': 'user', 'content': user_message},
23
+ ]