Mubin1917 commited on
Commit
c0e82c0
1 Parent(s): 857dbaf
Files changed (3) hide show
  1. app.py +36 -33
  2. requirements.txt +2 -1
  3. youtube_FC_14.py +238 -187
app.py CHANGED
@@ -5,7 +5,7 @@ from youtube_transcript_api import YouTubeTranscriptApi
5
  from langchain_openai import ChatOpenAI
6
  from langchain.agents import AgentExecutor
7
  from langchain.memory import ConversationBufferWindowMemory
8
- from youtube_FC_14 import YouTubeTranscriptTool, MainPointsExtractor, SummaryExtractor, YouTubeAgent
9
 
10
  import logging
11
  logging.getLogger().setLevel(logging.ERROR)
@@ -13,26 +13,20 @@ logging.getLogger().setLevel(logging.ERROR)
13
  import warnings
14
  warnings.filterwarnings("ignore")
15
 
 
 
 
 
16
  class ChatBot:
17
  def __init__(self):
18
- self.youtube_agent = None
19
- self.api_key = None
20
-
21
- def initialize_agent(self, api_key):
22
- if api_key:
23
- os.environ['OPENAI_API_KEY'] = api_key
24
- openai.api_key = api_key
25
- self.api_key = api_key
26
- self.youtube_agent = YouTubeAgent()
27
- return "API key set successfully. Agent initialized."
28
- else:
29
- return "Please provide a valid API key."
30
 
31
- def chat(self, message, history):
32
- if not self.youtube_agent:
33
- return "Please set your OpenAI API key first."
34
-
35
  try:
 
 
 
 
36
  response = self.youtube_agent.invoke(message)
37
  return response
38
  except Exception as e:
@@ -40,15 +34,12 @@ class ChatBot:
40
 
41
  chatbot = ChatBot() # Create an instance of ChatBot
42
 
43
- def set_api_key(api_key):
44
- return chatbot.initialize_agent(api_key)
45
-
46
  def user_message(message, history):
47
  return "", history + [[message, None]]
48
 
49
- def bot_message(history):
50
  user_message = history[-1][0]
51
- bot_response = chatbot.chat(user_message, history)
52
  history[-1][1] = bot_response
53
  return history
54
 
@@ -60,19 +51,33 @@ example_messages = [
60
  "What tools are available for use?",
61
  "What is the following video about? https://www.youtube.com/watch?v=dZxbVGhpEkI",
62
  "Can you summarize this video? https://www.youtube.com/watch?v=hM8unyUM6KA",
63
- "Extract the main points from this video: https://www.youtube.com/watch?v=UF8uR6Z6KLc"
 
 
64
  ]
65
 
66
  with gr.Blocks() as demo:
67
- gr.Markdown("# YouTube Video Analysis Chatbot")
 
68
 
69
- with gr.Row():
70
- api_key_input = gr.Textbox(type="password", label="Enter your OpenAI API key")
71
- api_key_button = gr.Button("Set API Key")
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- api_key_status = gr.Textbox(label="API Key Status", interactive=False)
74
 
75
- chatbot_interface = gr.Chatbot()
76
  msg = gr.Textbox(label="Message")
77
 
78
  with gr.Row():
@@ -82,14 +87,12 @@ with gr.Blocks() as demo:
82
  gr.Markdown("## Example Messages")
83
  example_btns = [gr.Button(i) for i in example_messages]
84
 
85
- api_key_button.click(set_api_key, inputs=api_key_input, outputs=api_key_status)
86
-
87
  submit_btn.click(user_message, [msg, chatbot_interface], [msg, chatbot_interface], queue=False).then(
88
- bot_message, chatbot_interface, chatbot_interface
89
  )
90
 
91
  msg.submit(user_message, [msg, chatbot_interface], [msg, chatbot_interface], queue=False).then(
92
- bot_message, chatbot_interface, chatbot_interface
93
  )
94
 
95
  clear_btn.click(lambda: None, None, chatbot_interface, queue=False)
 
5
  from langchain_openai import ChatOpenAI
6
  from langchain.agents import AgentExecutor
7
  from langchain.memory import ConversationBufferWindowMemory
8
+ from FCnew18thJul import YouTubeAgent, set_temperature
9
 
10
  import logging
11
  logging.getLogger().setLevel(logging.ERROR)
 
13
  import warnings
14
  warnings.filterwarnings("ignore")
15
 
16
+ from dotenv import load_dotenv, find_dotenv
17
+ _ = load_dotenv(find_dotenv()) # read local .env file
18
+ openai.api_key = os.environ['OPENAI_API_KEY']
19
+
20
  class ChatBot:
21
  def __init__(self):
22
+ self.youtube_agent = YouTubeAgent()
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ def chat(self, message, history, temperature):
 
 
 
25
  try:
26
+ # Set the temperature using the function from FCnew18thJul.py
27
+ set_temperature(temperature)
28
+ # Reinitialize the agent to use the new temperature
29
+ self.youtube_agent = YouTubeAgent()
30
  response = self.youtube_agent.invoke(message)
31
  return response
32
  except Exception as e:
 
34
 
35
  chatbot = ChatBot() # Create an instance of ChatBot
36
 
 
 
 
37
  def user_message(message, history):
38
  return "", history + [[message, None]]
39
 
40
+ def bot_message(history, temperature):
41
  user_message = history[-1][0]
42
+ bot_response = chatbot.chat(user_message, history, temperature)
43
  history[-1][1] = bot_response
44
  return history
45
 
 
51
  "What tools are available for use?",
52
  "What is the following video about? https://www.youtube.com/watch?v=dZxbVGhpEkI",
53
  "Can you summarize this video? https://www.youtube.com/watch?v=hM8unyUM6KA",
54
+ "Extract the main points from this video: https://www.youtube.com/watch?v=UF8uR6Z6KLc",
55
+ "What are the main challenges discussed in the video? https://www.youtube.com/watch?v=-OSxeoIAs2w&t=4262s",
56
+ "What is the speakers name in this video? dZxbVGhpEkI"
57
  ]
58
 
59
  with gr.Blocks() as demo:
60
+ gr.Markdown("""
61
+ # Chat with YouTube Videos
62
 
63
+ This application provides a comprehensive set of tools for analyzing YouTube videos,
64
+ extracting information, and answering questions based on video content. It leverages
65
+ the LangChain library for natural language processing tasks and the YouTube Transcript
66
+ API for fetching video transcripts.
67
+
68
+ Key Features:
69
+ - Main points summarization in multiple formats
70
+ - Video content summarization
71
+ - Question answering based on video content
72
+ - Flexible AI agent for handling various YouTube video-related tasks
73
+
74
+ Simply enter your question or request along with a YouTube video link, and the AI will process and respond accordingly.
75
+ Adjust the temperature slider to control the creativity of the AI's responses.
76
+ """)
77
 
78
+ temperature_slider = gr.Slider(minimum=0, maximum=1, step=0.1, label="Temperature", value=0)
79
 
80
+ chatbot_interface = gr.Chatbot(show_copy_button=True)
81
  msg = gr.Textbox(label="Message")
82
 
83
  with gr.Row():
 
87
  gr.Markdown("## Example Messages")
88
  example_btns = [gr.Button(i) for i in example_messages]
89
 
 
 
90
  submit_btn.click(user_message, [msg, chatbot_interface], [msg, chatbot_interface], queue=False).then(
91
+ bot_message, [chatbot_interface, temperature_slider], chatbot_interface
92
  )
93
 
94
  msg.submit(user_message, [msg, chatbot_interface], [msg, chatbot_interface], queue=False).then(
95
+ bot_message, [chatbot_interface, temperature_slider], chatbot_interface
96
  )
97
 
98
  clear_btn.click(lambda: None, None, chatbot_interface, queue=False)
requirements.txt CHANGED
@@ -9,4 +9,5 @@ langchain-core==0.2.19
9
  langchain-openai==0.1.16
10
  langchain-text-splitters==0.2.2
11
  pyperclip==1.9.0
12
- openai==1.35.13
 
 
9
  langchain-openai==0.1.16
10
  langchain-text-splitters==0.2.2
11
  pyperclip==1.9.0
12
+ openai==1.35.13
13
+ python-dotenv
youtube_FC_14.py CHANGED
@@ -1,164 +1,71 @@
1
  """
2
- YouTube Video Analysis Module
3
 
4
- This module provides tools for analyzing YouTube videos, including transcript extraction
5
- and main points summarization. It uses the LangChain library for natural language
6
- processing tasks and the YouTube Transcript API for fetching video transcripts.
 
7
 
8
  Classes:
9
- YouTubeTranscriptTool: Handles fetching and processing of YouTube video transcripts.
10
- MainPointsExtractor: Extracts and formats main points from YouTube video transcripts.
11
- YouTubeAgent: Manages the overall agent setup for interacting with YouTube videos.
12
- SummaryExtractor: Extracts summaries from YouTube video transcripts.
13
-
14
- Usage:
15
- youtube_agent = YouTubeAgent()
16
- video_link = "https://www.youtube.com/watch?v=VIDEO_ID"
17
- results = process_video(video_link, youtube_agent)
 
 
 
 
 
 
 
18
  """
19
 
20
  import os
21
  import openai
22
- from typing import List, Dict, Any
23
  from youtube_transcript_api import YouTubeTranscriptApi
24
  from langchain_core.pydantic_v1 import BaseModel, Field
25
  from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
26
  from langchain_openai import ChatOpenAI
27
  from langchain.schema.runnable import RunnableLambda, RunnablePassthrough
28
  from langchain.agents import tool, AgentExecutor
29
- from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser
30
  from langchain.text_splitter import RecursiveCharacterTextSplitter
31
  from langchain_core.utils.function_calling import convert_to_openai_function
32
  from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser
33
  from langchain.agents.format_scratchpad import format_to_openai_functions
34
  from langchain.memory import ConversationBufferWindowMemory
35
- from functools import wraps
36
- import functools
37
- import logging
38
- import traceback
39
-
40
- # Set up logging with more detailed format
41
- logging.basicConfig(level=logging.INFO,
42
- format='%(asctime)s - %(levelname)s - %(name)s - %(filename)s:%(lineno)d - %(message)s')
43
- logger = logging.getLogger(__name__)
44
-
45
- # Define a decorator for error logging
46
- def log_errors(func):
47
- @wraps(func)
48
- def wrapper(*args, **kwargs):
49
- try:
50
- return func(*args, **kwargs)
51
- except Exception as e:
52
- logger.error(f"Error in {func.__name__}: {str(e)}")
53
- logger.error(f"Traceback: {traceback.format_exc()}")
54
- raise
55
- return wrapper
56
-
57
- class YouTubeTranscriptTool:
58
- """
59
- A tool for fetching and processing YouTube video transcripts.
60
-
61
- This class provides methods to retrieve transcripts with or without timestamps,
62
- and to split transcripts into manageable chunks.
63
- """
64
-
65
- @staticmethod
66
- @tool(return_direct=True)
67
- def get_transcript_with_timestamps(youtube_video_id: str, chunk_number: int = 0) -> str:
68
- """
69
- Retrieves a YouTube video transcript with timestamps.
70
-
71
- Args:
72
- youtube_video_id (str): The ID of the YouTube video.
73
- chunk_number (int): The index of the transcript chunk to retrieve.
74
-
75
- Returns:
76
- str: The requested transcript chunk with timestamps.
77
- """
78
- return YouTubeTranscriptTool._get_transcript(youtube_video_id, chunk_number, include_timestamps=True)
79
-
80
- @staticmethod
81
- @tool(return_direct=True)
82
- def get_transcript_without_timestamps(youtube_video_id: str, chunk_number: int = 0) -> str:
83
- """
84
- Retrieves a YouTube video transcript without timestamps.
85
-
86
- Args:
87
- youtube_video_id (str): The ID of the YouTube video.
88
- chunk_number (int): The index of the transcript chunk to retrieve.
89
-
90
- Returns:
91
- str: The requested transcript chunk without timestamps.
92
- """
93
- return YouTubeTranscriptTool._get_transcript(youtube_video_id, chunk_number, include_timestamps=False)
94
-
95
- @staticmethod
96
- @log_errors
97
- def _get_transcript(youtube_video_id: str, chunk_number: int, include_timestamps: bool) -> str:
98
- """
99
- Internal method to fetch and process the transcript.
100
-
101
- Args:
102
- youtube_video_id (str): The ID of the YouTube video.
103
- chunk_number (int): The index of the transcript chunk to retrieve.
104
- include_timestamps (bool): Whether to include timestamps in the transcript.
105
-
106
- Returns:
107
- str: The processed transcript chunk.
108
-
109
- Raises:
110
- ValueError: If the requested chunk number is out of range.
111
- """
112
- try:
113
- transcript_json = YouTubeTranscriptApi.get_transcript(youtube_video_id)
114
- text_splitter = RecursiveCharacterTextSplitter(
115
- chunk_size=8192,
116
- chunk_overlap=0,
117
- separators=[f" {char}" for char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"]
118
- )
119
-
120
- if include_timestamps:
121
- transcript_data = [f"{entry['start']:.2f}: {entry['text']} " for entry in transcript_json]
122
- else:
123
- transcript_data = [entry['text'] for entry in transcript_json]
124
 
125
- transcript_text = " ".join(transcript_data)
126
- transcript_splits = text_splitter.split_text(transcript_text)
127
-
128
- if chunk_number >= len(transcript_splits):
129
- raise ValueError(f"Chunk number {chunk_number} is out of range. Total chunks: {len(transcript_splits)}")
130
-
131
- chunked_text = transcript_splits[chunk_number]
132
 
133
- return YouTubeTranscriptTool._format_response(transcript_splits, chunk_number, chunked_text)
134
- except Exception as e:
135
- logger.error(f"Error in _get_transcript: {str(e)}")
136
- return f"Error fetching transcript: {str(e)}"
137
 
138
- @staticmethod
139
- def _format_response(transcript_splits: List[str], chunk_number: int, chunked_text: str) -> str:
140
- """
141
- Formats the transcript chunk response.
 
 
142
 
143
- Args:
144
- transcript_splits (List[str]): All transcript chunks.
145
- chunk_number (int): The index of the current chunk.
146
- chunked_text (str): The text of the current chunk.
 
 
147
 
148
- Returns:
149
- str: Formatted response string.
150
- """
151
- if len(transcript_splits) == 1:
152
- return f"Note: Complete subtitles returned.\n\nSubtitles:{chunked_text}"
153
- elif chunk_number == len(transcript_splits) - 1:
154
- return f"Note: Last chunk of subtitles returned.\n\nSubtitles:{chunked_text}"
155
- else:
156
- return f"Note: Partial subtitles returned. To get the next chunk, use chunk_number = {chunk_number + 1}.\n\nSubtitles:{chunked_text}"
157
-
158
- class Points(BaseModel):
159
  """Pydantic model for representing extracted points."""
160
- point: str = Field(description="The main topic, theme, or subject extracted from the subtitle.")
161
- context: str = Field(description="The context or brief explanation of the main point.")
162
  emoji: str = Field(description="An emoji that represents or summarizes the main point.")
163
  timestamp: float = Field(description="The timestamp (in floating-point number) from the video where the main point is mentioned.")
164
 
@@ -170,16 +77,19 @@ class MainPointsExtractor:
170
  using natural language processing techniques.
171
  """
172
 
173
- class Info(BaseModel):
174
  """Pydantic model for representing a collection of points."""
175
- points: List[Points]
 
 
 
 
176
 
177
  @staticmethod
178
  @tool(return_direct=True)
179
- @log_errors
180
  def get_youtube_video_main_points(youtube_video_id: str) -> str:
181
  """
182
- Extracts and formats main points from a YouTube video transcript.
183
 
184
  Args:
185
  youtube_video_id (str): The ID of the YouTube video.
@@ -189,14 +99,14 @@ class MainPointsExtractor:
189
  """
190
  try:
191
  transcript = MainPointsExtractor._get_youtube_video_transcript(youtube_video_id)
192
- main_points = MainPointsExtractor._extract_main_points(transcript)
193
- return MainPointsExtractor._format_youtube_comment(main_points)
 
 
194
  except Exception as e:
195
- logger.error(f"Error in get_youtube_video_main_points: {str(e)}")
196
- return f"Error extracting main points: {str(e)}"
197
 
198
  @staticmethod
199
- @log_errors
200
  def _get_youtube_video_transcript(youtube_video_id: str) -> str:
201
  """
202
  Fetches the transcript for a YouTube video.
@@ -215,47 +125,66 @@ class MainPointsExtractor:
215
  transcript_data = [f"{entry['start']:.2f}: {entry['text']} " for entry in transcript_json]
216
  return "".join(transcript_data)
217
  except Exception as e:
218
- logger.error(f"Error fetching transcript: {str(e)}")
219
  raise
220
 
221
  @staticmethod
222
- @functools.lru_cache(maxsize=16)
223
- def _extract_main_points(transcript: str) -> List[Dict[str, Any]]:
224
  """
225
  Extracts main points from the transcript using NLP techniques.
226
-
227
- This method is cached to improve performance for repeated calls.
228
 
229
  Args:
230
  transcript (str): The full transcript of the video.
231
-
232
  Returns:
233
  List[Dict[str, Any]]: A list of dictionaries containing extracted main points.
234
  """
235
- main_points_extraction_function = [convert_to_openai_function(MainPointsExtractor.Info)]
236
 
237
- model = ChatOpenAI(temperature=0)
238
- extraction_model = model.bind(functions=main_points_extraction_function, function_call={"name": "Info"})
239
 
240
- prompt = ChatPromptTemplate.from_messages([("human", "{input}")])
241
- extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name="points")
 
 
 
 
242
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  text_splitter = RecursiveCharacterTextSplitter(chunk_overlap=0, chunk_size=8192, separators=[f" {char}" for char in "123456789"])
244
 
245
  prep = RunnableLambda(lambda x: [{"input": doc} for doc in text_splitter.split_text(x)])
246
 
247
- chain = prep | extraction_chain.map() | MainPointsExtractor._flatten
248
 
249
- return chain.invoke(transcript)
 
 
250
 
251
  @staticmethod
252
- @log_errors
253
  def _flatten(matrix):
254
  """Flattens a 2D list into a 1D list."""
255
  return [item for row in matrix for item in row]
256
 
257
  @staticmethod
258
- @log_errors
259
  def _format_youtube_comment(json_data: List[Dict[str, Any]]) -> str:
260
  """
261
  Formats extracted main points into a YouTube-style comment.
@@ -276,11 +205,18 @@ class MainPointsExtractor:
276
  for entry in json_data:
277
  timestamp = _format_timestamp(entry['timestamp'])
278
  emoji = entry['emoji']
279
- point = entry['point']
280
- context = entry['context']
281
- formatted_comment += f"{timestamp} {emoji} {point}: {context}\n"
 
 
 
 
 
282
 
283
  return formatted_comment.strip()
 
 
284
 
285
  class Summary(BaseModel):
286
  """Pydantic model for representing extracted summary."""
@@ -300,7 +236,6 @@ class SummaryExtractor:
300
 
301
  @staticmethod
302
  @tool(return_direct=False)
303
- @log_errors
304
  def get_youtube_video_summary(youtube_video_id: str) -> str:
305
  """
306
  Extracts and formats a summary from a YouTube video transcript.
@@ -316,11 +251,9 @@ class SummaryExtractor:
316
  summary = SummaryExtractor._extract_summary(transcript)
317
  return SummaryExtractor._format_summary(summary)
318
  except Exception as e:
319
- logger.error(f"Error in get_youtube_video_summary: {str(e)}")
320
  return f"Error extracting summary: {str(e)}"
321
 
322
  @staticmethod
323
- @log_errors
324
  def _get_youtube_video_transcript(youtube_video_id: str) -> str:
325
  """
326
  Fetches the transcript for a YouTube video.
@@ -339,11 +272,9 @@ class SummaryExtractor:
339
  transcript_data = [entry['text'] for entry in transcript_json]
340
  return " ".join(transcript_data)
341
  except Exception as e:
342
- logger.error(f"Error fetching transcript: {str(e)}")
343
  raise
344
 
345
  @staticmethod
346
- @functools.lru_cache(maxsize=16)
347
  def _extract_summary(transcript: str) -> List[Summary]:
348
  """
349
  Extracts a summary from a YouTube video transcript.
@@ -356,8 +287,9 @@ class SummaryExtractor:
356
  """
357
  summary_extraction_function = [convert_to_openai_function(SummaryExtractor.Info)]
358
 
359
- model = ChatOpenAI(temperature=0)
360
- extraction_model = model.bind(functions=summary_extraction_function, function_call={"name": "Info"})
 
361
 
362
  prompt = ChatPromptTemplate.from_messages([("human", "{input}")])
363
 
@@ -382,6 +314,128 @@ class SummaryExtractor:
382
  """
383
  return "\n\n".join([s["summary"] for s in summaries])
384
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  class YouTubeAgent:
386
  """
387
  An agent for interacting with YouTube videos and processing user queries.
@@ -392,28 +446,33 @@ class YouTubeAgent:
392
 
393
  def __init__(self):
394
  """Initializes the YouTubeAgent with necessary tools and components."""
 
395
  self.tools = [
396
- YouTubeTranscriptTool.get_transcript_with_timestamps,
397
- YouTubeTranscriptTool.get_transcript_without_timestamps,
398
  MainPointsExtractor.get_youtube_video_main_points,
399
- SummaryExtractor.get_youtube_video_summary
 
400
  ]
 
401
  self.sys_message = "You are a helpful assistant."
 
402
  self.functions = [convert_to_openai_function(f) for f in self.tools]
403
- self.model = ChatOpenAI(temperature=0).bind(functions=self.functions)
 
 
404
  self.prompt = ChatPromptTemplate.from_messages([
405
  ("system", self.sys_message),
406
  MessagesPlaceholder(variable_name="history"),
407
  ("user", "{input}"),
408
  MessagesPlaceholder(variable_name="agent_scratchpad")
409
  ])
 
410
  self.agent_chain = RunnablePassthrough.assign(
411
  agent_scratchpad= lambda x: format_to_openai_functions(x["intermediate_steps"])
412
  ) | self.prompt | self.model | OpenAIFunctionsAgentOutputParser()
 
413
  self.memory = ConversationBufferWindowMemory(k=3, return_messages=True, memory_key="history")
414
  self.agent_executor = AgentExecutor(agent=self.agent_chain, tools=self.tools, memory=self.memory)
415
-
416
- @log_errors
417
  def invoke(self, input_text: str) -> str:
418
  """
419
  Processes a user input and returns the agent's response.
@@ -428,16 +487,8 @@ class YouTubeAgent:
428
  result = self.agent_executor.invoke({"input": input_text})
429
  return result['output']
430
  except Exception as e:
431
- logger.error(f"Error in YouTubeAgent.invoke: {str(e)}")
432
  return f"An error occurred: {str(e)}"
433
 
434
- # # Usage example
435
- # if __name__ == "__main__":
436
- # youtube_agent = YouTubeAgent()
437
- # video_link = "https://www.youtube.com/watch?v=dZxbVGhpEkI"
438
- # try:
439
- # main_points = youtube_agent.invoke(f"Can you get summary of the following video {video_link}")
440
- # except Exception as e:
441
- # logger.error(f"An error occurred during processing: {str(e)}")
442
- # print(f"An error occurred: {str(e)}")
443
-
 
1
  """
2
+ YouTube Video Analysis and Interaction Module
3
 
4
+ This module provides a comprehensive set of tools for analyzing YouTube videos,
5
+ extracting information, and answering questions based on video content. It leverages
6
+ the LangChain library for natural language processing tasks and the YouTube Transcript
7
+ API for fetching video transcripts.
8
 
9
  Classes:
10
+ MainPointsExtractor:
11
+ Extracts and formats main points from YouTube video transcripts.
12
+ Timestamps are formatted for direct use in YouTube comments, enabling clickable
13
+ links to specific video sections when pasted.
14
+ SummaryExtractor:
15
+ Handles the extraction and formatting of video summaries.
16
+ QuestionAnswerExtractor:
17
+ Processes user questions and extracts answers from video transcripts.
18
+ YouTubeAgent:
19
+ Manages the overall agent setup for interacting with YouTube videos and processing user queries.
20
+
21
+ Key Features:
22
+ - Main points summarization in multiple formats
23
+ - Video content summarization
24
+ - Question answering based on video content
25
+ - Flexible AI agent for handling various YouTube video-related tasks
26
  """
27
 
28
  import os
29
  import openai
30
+ from typing import List, Dict, Any, Union, Type
31
  from youtube_transcript_api import YouTubeTranscriptApi
32
  from langchain_core.pydantic_v1 import BaseModel, Field
33
  from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
34
  from langchain_openai import ChatOpenAI
35
  from langchain.schema.runnable import RunnableLambda, RunnablePassthrough
36
  from langchain.agents import tool, AgentExecutor
37
+ from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser, JsonOutputFunctionsParser
38
  from langchain.text_splitter import RecursiveCharacterTextSplitter
39
  from langchain_core.utils.function_calling import convert_to_openai_function
40
  from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser
41
  from langchain.agents.format_scratchpad import format_to_openai_functions
42
  from langchain.memory import ConversationBufferWindowMemory
43
+ from dotenv import load_dotenv, find_dotenv
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ _ = load_dotenv(find_dotenv()) # read local .env file
46
+ openai.api_key = os.environ['OPENAI_API_KEY']
 
 
 
 
 
47
 
48
+ def get_temperature():
49
+ return 0 #Default value
 
 
50
 
51
+ def set_temperature(new_temperature):
52
+ global get_temperature
53
+ def new_get_temperature():
54
+ return new_temperature
55
+ get_temperature = new_get_temperature
56
+ # print(f"Temperature set to: {get_temperature()}")
57
 
58
+ class Points_1(BaseModel):
59
+ """Pydantic model for representing extracted points from Youtube-Transcript"""
60
+ timestamp: float = Field(description="The timestamp (in floating-point number) of when main points are discussed or talked about in the video.")
61
+ main_point: str = Field(description="A title for Main point.")
62
+ summary: str = Field(description="A summary of main points discussed at that timestamp. End with fullstop.")
63
+ emoji: str = Field(description="An emoji that matches the summary.")
64
 
65
+ class Points_2(BaseModel):
 
 
 
 
 
 
 
 
 
 
66
  """Pydantic model for representing extracted points."""
67
+ main_point: str = Field(description="The main topic, theme, or subject extracted from the subtitle.")
68
+ summary: str = Field(description="The context or brief explanation of the main point.")
69
  emoji: str = Field(description="An emoji that represents or summarizes the main point.")
70
  timestamp: float = Field(description="The timestamp (in floating-point number) from the video where the main point is mentioned.")
71
 
 
77
  using natural language processing techniques.
78
  """
79
 
80
+ class Info_1(BaseModel):
81
  """Pydantic model for representing a collection of points."""
82
+ points: List[Points_1]
83
+
84
+ class Info_2(BaseModel):
85
+ """Pydantic model for representing a collection of points."""
86
+ points: List[Points_2]
87
 
88
  @staticmethod
89
  @tool(return_direct=True)
 
90
  def get_youtube_video_main_points(youtube_video_id: str) -> str:
91
  """
92
+ Extracts and formats main points with Timestamps from YouTube video transcripts. Timestamps are formatted for direct use in YouTube comments, enabling clickable links to specific video sections when pasted.
93
 
94
  Args:
95
  youtube_video_id (str): The ID of the YouTube video.
 
99
  """
100
  try:
101
  transcript = MainPointsExtractor._get_youtube_video_transcript(youtube_video_id)
102
+ main_points_1 = MainPointsExtractor._extract_main_points(transcript, MainPointsExtractor.Info_1)
103
+ main_points_2 = MainPointsExtractor._extract_main_points(transcript, MainPointsExtractor.Info_2)
104
+ formatted_output = f"""Main points extracted from YouTube video (ID: {youtube_video_id})\nStyle_1:\n```\n{main_points_2}\n```\nStyle_2:\n```\n{main_points_1}\n```\nChoose the style that best suits your needs for presenting the main points of the video."""
105
+ return formatted_output
106
  except Exception as e:
107
+ raise
 
108
 
109
  @staticmethod
 
110
  def _get_youtube_video_transcript(youtube_video_id: str) -> str:
111
  """
112
  Fetches the transcript for a YouTube video.
 
125
  transcript_data = [f"{entry['start']:.2f}: {entry['text']} " for entry in transcript_json]
126
  return "".join(transcript_data)
127
  except Exception as e:
 
128
  raise
129
 
130
  @staticmethod
131
+ def _extract_main_points(transcript: str, info_model: Union[Type[Info_1], Type[Info_2]]) -> List[Dict[str, Any]]:
 
132
  """
133
  Extracts main points from the transcript using NLP techniques.
134
+
135
+ This method maintains a conversation history to provide context for subsequent calls.
136
 
137
  Args:
138
  transcript (str): The full transcript of the video.
139
+
140
  Returns:
141
  List[Dict[str, Any]]: A list of dictionaries containing extracted main points.
142
  """
143
+ main_points_extraction_function = [convert_to_openai_function(info_model)]
144
 
145
+ model = ChatOpenAI(temperature=get_temperature())
 
146
 
147
+ extraction_model = model.bind(functions=main_points_extraction_function)
148
+
149
+ system_message = f"""
150
+ You are an AI assistant that extracts info from video transcripts.
151
+ When extracting info, ensure that:
152
+ 1. Each point has a unique timestamp.
153
 
154
+ In addition to these specific requirements, you have the authority to make other improvements as you see fit. This may include:
155
+
156
+ - Refining the summaries for clarity and conciseness
157
+ - Adjusting emoji choices to better represent the content
158
+ - Reorganizing points for better logical flow
159
+ - Removing redundant information
160
+ - Adding context where necessary
161
+
162
+ Your goal is to produce a refined and accurate representation of the main points from the video transcript. Use your judgment to balance adherence to the specific rules with overall improvement of the extracted information.
163
+ """
164
+
165
+ prompt = ChatPromptTemplate.from_messages([
166
+ ("system", system_message),
167
+ ("human", "{input}")
168
+ ])
169
+
170
+ extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name="points")
171
+
172
  text_splitter = RecursiveCharacterTextSplitter(chunk_overlap=0, chunk_size=8192, separators=[f" {char}" for char in "123456789"])
173
 
174
  prep = RunnableLambda(lambda x: [{"input": doc} for doc in text_splitter.split_text(x)])
175
 
176
+ chain = prep | extraction_chain.map() | MainPointsExtractor._flatten | MainPointsExtractor._format_youtube_comment
177
 
178
+ result_1 = chain.invoke(transcript)
179
+
180
+ return result_1
181
 
182
  @staticmethod
 
183
  def _flatten(matrix):
184
  """Flattens a 2D list into a 1D list."""
185
  return [item for row in matrix for item in row]
186
 
187
  @staticmethod
 
188
  def _format_youtube_comment(json_data: List[Dict[str, Any]]) -> str:
189
  """
190
  Formats extracted main points into a YouTube-style comment.
 
205
  for entry in json_data:
206
  timestamp = _format_timestamp(entry['timestamp'])
207
  emoji = entry['emoji']
208
+ summary = entry['summary']
209
+
210
+ if entry['main_point'].endswith('.'):
211
+ point = entry['main_point'][:-1]
212
+ else:
213
+ point = entry['main_point']
214
+
215
+ formatted_comment += f"{timestamp} {emoji} {point}: {summary}\n"
216
 
217
  return formatted_comment.strip()
218
+
219
+ #######################################################################################################################################
220
 
221
  class Summary(BaseModel):
222
  """Pydantic model for representing extracted summary."""
 
236
 
237
  @staticmethod
238
  @tool(return_direct=False)
 
239
  def get_youtube_video_summary(youtube_video_id: str) -> str:
240
  """
241
  Extracts and formats a summary from a YouTube video transcript.
 
251
  summary = SummaryExtractor._extract_summary(transcript)
252
  return SummaryExtractor._format_summary(summary)
253
  except Exception as e:
 
254
  return f"Error extracting summary: {str(e)}"
255
 
256
  @staticmethod
 
257
  def _get_youtube_video_transcript(youtube_video_id: str) -> str:
258
  """
259
  Fetches the transcript for a YouTube video.
 
272
  transcript_data = [entry['text'] for entry in transcript_json]
273
  return " ".join(transcript_data)
274
  except Exception as e:
 
275
  raise
276
 
277
  @staticmethod
 
278
  def _extract_summary(transcript: str) -> List[Summary]:
279
  """
280
  Extracts a summary from a YouTube video transcript.
 
287
  """
288
  summary_extraction_function = [convert_to_openai_function(SummaryExtractor.Info)]
289
 
290
+ model = ChatOpenAI(temperature=get_temperature())
291
+
292
+ extraction_model = model.bind(functions=summary_extraction_function)
293
 
294
  prompt = ChatPromptTemplate.from_messages([("human", "{input}")])
295
 
 
314
  """
315
  return "\n\n".join([s["summary"] for s in summaries])
316
 
317
+ #############################################################################################################################################################
318
+
319
+ class Answer(BaseModel):
320
+ """Pydantic model for representing an answer to a question."""
321
+ answer: str = Field(description="The answer to the user's question based on the video transcript.")
322
+ confidence: float = Field(description="A confidence score between 0 and 1 indicating how certain the model is about the answer.")
323
+
324
+ class QuestionAnswerExtractor:
325
+ """
326
+ A tool for answering questions about YouTube videos based on their transcripts.
327
+
328
+ This class provides methods to process transcripts and generate answers to user questions
329
+ using natural language processing techniques.
330
+ """
331
+
332
+ class Info(BaseModel):
333
+ """Pydantic model for representing a collection of answers."""
334
+ answers: List[Answer]
335
+
336
+ @staticmethod
337
+ @tool(return_direct=True)
338
+ def get_answer(youtube_video_id: str, question: str) -> str:
339
+ """
340
+ Answers a question about a YouTube video based on its transcript.
341
+
342
+ Args:
343
+ youtube_video_id (str): The ID of the YouTube video.
344
+ question (str): The user's question about the video.
345
+
346
+ Returns:
347
+ str: Formatted string containing the answer to the user's question.
348
+ """
349
+ try:
350
+ transcript = QuestionAnswerExtractor._get_youtube_video_transcript(youtube_video_id)
351
+ answer = QuestionAnswerExtractor._extract_answer(transcript, question)
352
+ return QuestionAnswerExtractor._format_answer(answer)
353
+ except Exception as e:
354
+ return f"Error answering question: {str(e)}"
355
+
356
+ @staticmethod
357
+ def _get_youtube_video_transcript(youtube_video_id: str) -> str:
358
+ """
359
+ Fetches the transcript for a YouTube video.
360
+
361
+ Args:
362
+ youtube_video_id (str): The ID of the YouTube video.
363
+
364
+ Returns:
365
+ str: The full transcript of the video.
366
+
367
+ Raises:
368
+ Exception: If there's an error fetching the transcript.
369
+ """
370
+ try:
371
+ transcript_json = YouTubeTranscriptApi.get_transcript(youtube_video_id)
372
+ transcript_data = [entry['text'] for entry in transcript_json]
373
+ return " ".join(transcript_data)
374
+ except Exception as e:
375
+ raise
376
+
377
+ @staticmethod
378
+ def _extract_answer(transcript: str, question: str) -> List[Answer]:
379
+ """
380
+ Extracts an answer to the user's question from the YouTube video transcript.
381
+
382
+ Args:
383
+ transcript (str): The full transcript of the video.
384
+ question (str): The user's question about the video.
385
+
386
+ Returns:
387
+ List[Answer]: A list of Answer objects containing the extracted answers.
388
+ """
389
+ answer_extraction_function = [convert_to_openai_function(QuestionAnswerExtractor.Info)]
390
+
391
+ model = ChatOpenAI(temperature=get_temperature())
392
+ extraction_model = model.bind(functions=answer_extraction_function, function_call={"name": "Info"})
393
+
394
+ prompt = ChatPromptTemplate.from_messages([
395
+ ("system", "You are an AI assistant tasked with answering questions about a video based on its transcript."),
396
+ ("human", "Transcript: {transcript}\n\nQuestion: {question}\n\nProvide an answer to the question based on the transcript, along with a confidence score.")
397
+ ])
398
+
399
+ extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name="answers")
400
+
401
+ text_splitter = RecursiveCharacterTextSplitter(chunk_overlap=192, chunk_size=8000, separators=[f" {char}" for char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"])
402
+
403
+ def prepare_input(x):
404
+ chunks = text_splitter.split_text(x['transcript'])
405
+ return [{"transcript": chunk, "question": x['question']} for chunk in chunks]
406
+
407
+ prep = RunnableLambda(prepare_input)
408
+
409
+ chain = prep | extraction_chain.map() | QuestionAnswerExtractor._flatten
410
+
411
+ return chain.invoke({"transcript": transcript, "question": question})
412
+
413
+ @staticmethod
414
+ def _flatten(matrix):
415
+ """Flattens a 2D list into a 1D list."""
416
+ return [item for row in matrix for item in row]
417
+
418
+ @staticmethod
419
+ def _format_answer(answers: List[Answer]) -> str:
420
+ """
421
+ Formats the list of answers into a single string.
422
+
423
+ Args:
424
+ answers (List[Answer]): List of Answer objects.
425
+
426
+ Returns:
427
+ str: A formatted string containing the best answer and its confidence score.
428
+ """
429
+ if not answers:
430
+ return "I couldn't find an answer to your question based on the video transcript."
431
+
432
+ # Sort answers by confidence score and take the best one
433
+ best_answer = max(answers, key=lambda x: x['confidence'])
434
+
435
+ return f"{best_answer['answer']}({best_answer['confidence']:.2f})"
436
+
437
+ #######################################################################################################################################
438
+
439
  class YouTubeAgent:
440
  """
441
  An agent for interacting with YouTube videos and processing user queries.
 
446
 
447
  def __init__(self):
448
  """Initializes the YouTubeAgent with necessary tools and components."""
449
+
450
  self.tools = [
 
 
451
  MainPointsExtractor.get_youtube_video_main_points,
452
+ SummaryExtractor.get_youtube_video_summary,
453
+ QuestionAnswerExtractor.get_answer
454
  ]
455
+
456
  self.sys_message = "You are a helpful assistant."
457
+
458
  self.functions = [convert_to_openai_function(f) for f in self.tools]
459
+
460
+ self.model = ChatOpenAI(temperature=get_temperature()).bind(functions=self.functions)
461
+
462
  self.prompt = ChatPromptTemplate.from_messages([
463
  ("system", self.sys_message),
464
  MessagesPlaceholder(variable_name="history"),
465
  ("user", "{input}"),
466
  MessagesPlaceholder(variable_name="agent_scratchpad")
467
  ])
468
+
469
  self.agent_chain = RunnablePassthrough.assign(
470
  agent_scratchpad= lambda x: format_to_openai_functions(x["intermediate_steps"])
471
  ) | self.prompt | self.model | OpenAIFunctionsAgentOutputParser()
472
+
473
  self.memory = ConversationBufferWindowMemory(k=3, return_messages=True, memory_key="history")
474
  self.agent_executor = AgentExecutor(agent=self.agent_chain, tools=self.tools, memory=self.memory)
475
+
 
476
  def invoke(self, input_text: str) -> str:
477
  """
478
  Processes a user input and returns the agent's response.
 
487
  result = self.agent_executor.invoke({"input": input_text})
488
  return result['output']
489
  except Exception as e:
 
490
  return f"An error occurred: {str(e)}"
491
 
492
+ # youtube_agent = YouTubeAgent()
493
+ # video_link = "https://www.youtube.com/watch?v=-OSxeoIAs2w"
494
+ # main_points = youtube_agent.invoke(f"The race involves which challenges in the following video {video_link}")