msaifee commited on
Commit
e49edab
ยท
1 Parent(s): 0914d48

Tavily search with parallel workflow

Browse files
Files changed (2) hide show
  1. app.py +90 -12
  2. requirements.txt +2 -1
app.py CHANGED
@@ -4,8 +4,10 @@ import os
4
  from langchain_groq import ChatGroq
5
  from typing_extensions import TypedDict
6
  from langgraph.graph import add_messages, StateGraph, END, START
7
- from langchain_core.messages import AIMessage, HumanMessage
8
- from typing import Annotated, List
 
 
9
 
10
  ## Langsmith Tracking
11
  os.environ["LANGCHAIN_TRACING_V2"] = "true"
@@ -15,6 +17,7 @@ os.environ["LANGCHAIN_PROJECT"]="Blog Generator Agent"
15
  class BlogState(TypedDict):
16
  topic: str
17
  title: str
 
18
  blog_content: Annotated[List, add_messages]
19
  reviewed_content: Annotated[List, add_messages]
20
  is_blog_ready: str
@@ -27,19 +30,47 @@ if 'graph' not in st.session_state:
27
  if 'graph_image' not in st.session_state:
28
  st.session_state.graph_image = None
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def init_graph(api_key: str):
31
 
32
- st.session_state.llm = ChatGroq(model="qwen-2.5-32b", api_key=api_key)
 
33
 
34
  builder = StateGraph(BlogState)
35
 
36
  builder.add_node("title_generator", generate_title)
 
37
  builder.add_node("content_generator", generate_content)
38
  builder.add_node("content_reviewer", review_content)
39
- builder.add_node("quality_check", evaluate_content)
40
-
41
  builder.add_edge(START, "title_generator")
 
42
  builder.add_edge("title_generator", "content_generator")
 
43
  builder.add_edge("content_generator", "content_reviewer")
44
  builder.add_edge("content_reviewer", "quality_check")
45
 
@@ -57,14 +88,49 @@ def generate_title(state: BlogState):
57
  - Attention-grabbing
58
  - Between 6-12 words"""
59
 
60
- with st.status("๐Ÿš€ Generating Titles..."):
61
- response = st.session_state.llm.invoke(prompt)
62
  state["title"] = response.content.split("\n")[0].strip('"')
63
  st.write(f"Selected title: **{state['title']}**")
64
  return state
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  def generate_content(state: BlogState):
67
- prompt = f"""Write a comprehensive blog post titled "{state["title"]}" with:
68
  1. Engaging introduction with hook
69
  2. 3-5 subheadings with detailed content
70
  3. Practical examples/statistics
@@ -73,7 +139,7 @@ def generate_content(state: BlogState):
73
  Style: Professional yet conversational (Flesch-Kincaid 60-70). Use markdown formatting"""
74
 
75
  with st.status("๐Ÿ“ Generating Content..."):
76
- response = st.session_state.llm.invoke(prompt)
77
  state["blog_content"].append(AIMessage(content=response.content))
78
  st.markdown(response.content)
79
  return state
@@ -88,7 +154,7 @@ def review_content(state: BlogState):
88
  Provide specific improvement suggestions. Content:\n{content}"""
89
 
90
  with st.status("๐Ÿ” Reviewing Content..."):
91
- feedback = st.session_state.llm.invoke(prompt)
92
  state["reviewed_content"].append(HumanMessage(content=feedback.content))
93
  st.write(feedback.content)
94
  return state
@@ -103,7 +169,7 @@ def evaluate_content(state: BlogState):
103
  Answer only Pass or Fail:"""
104
 
105
  with st.status("โœ… Evaluating Quality..."):
106
- response = st.session_state.llm.invoke(prompt)
107
  verdict = response.content.strip().upper()
108
  state["is_blog_ready"] = "Pass" if "PASS" in verdict else "Fail"
109
  state["reviewed_content"].append(AIMessage(
@@ -131,10 +197,20 @@ with st.sidebar:
131
  api_key = st.text_input("Groq API Key:",
132
  type="password",
133
  value=os.getenv("GROQ_API_KEY", ""))
134
- # Validate API key
 
135
  if not api_key:
136
  st.warning("โš ๏ธ Please enter your GROQ API key to proceed. Don't have? refer : https://console.groq.com/keys ")
 
 
 
 
 
137
 
 
 
 
 
138
  if st.button("Reset Session"):
139
  st.session_state.clear()
140
  st.rerun()
@@ -156,9 +232,11 @@ if generate_btn:
156
 
157
  # Initialize and run graph
158
  st.session_state.graph = init_graph(api_key)
 
159
  st.session_state.blog_state = BlogState(
160
  topic=topic,
161
  title="",
 
162
  blog_content=[],
163
  reviewed_content=[],
164
  is_blog_ready=""
 
4
  from langchain_groq import ChatGroq
5
  from typing_extensions import TypedDict
6
  from langgraph.graph import add_messages, StateGraph, END, START
7
+ from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
8
+ from typing import Annotated, List, Dict, Any
9
+ from langdetect import detect
10
+ from langchain_community.tools.tavily_search import TavilySearchResults
11
 
12
  ## Langsmith Tracking
13
  os.environ["LANGCHAIN_TRACING_V2"] = "true"
 
17
  class BlogState(TypedDict):
18
  topic: str
19
  title: str
20
+ search_results: Annotated[List[Dict[str, Any]], add_messages]
21
  blog_content: Annotated[List, add_messages]
22
  reviewed_content: Annotated[List, add_messages]
23
  is_blog_ready: str
 
30
  if 'graph_image' not in st.session_state:
31
  st.session_state.graph_image = None
32
 
33
+ # Helper function to detect English language
34
+ def is_english(text):
35
+ # Ensure we have enough text to analyze
36
+ if not text or len(text.strip()) < 50:
37
+ return False
38
+
39
+ try:
40
+ # Try primary language detection
41
+ return detect(text) == 'en'
42
+ except:
43
+ # If detection fails, use a more robust approach
44
+ common_english_words = ['the', 'and', 'in', 'to', 'of', 'is', 'for', 'with', 'on', 'that',
45
+ 'this', 'are', 'was', 'be', 'have', 'it', 'not', 'they', 'by', 'from']
46
+ text_lower = text.lower()
47
+ # Count occurrences of common English words
48
+ english_word_count = sum(1 for word in common_english_words if f" {word} " in f" {text_lower} ")
49
+ # Calculate ratio of English words to text length
50
+ text_words = len(text_lower.split())
51
+ if text_words == 0: # Avoid division by zero
52
+ return False
53
+
54
+ english_ratio = english_word_count / min(20, text_words) # Cap at 20 to avoid skew
55
+ return english_word_count >= 5 or english_ratio > 0.25 # More stringent criteria
56
+
57
  def init_graph(api_key: str):
58
 
59
+ global llm
60
+ llm = ChatGroq(model="qwen-2.5-32b", api_key=api_key)
61
 
62
  builder = StateGraph(BlogState)
63
 
64
  builder.add_node("title_generator", generate_title)
65
+ builder.add_node("search_web", search_web)
66
  builder.add_node("content_generator", generate_content)
67
  builder.add_node("content_reviewer", review_content)
68
+ builder.add_node("quality_check", evaluate_content) # New evaluation node
69
+
70
  builder.add_edge(START, "title_generator")
71
+ builder.add_edge(START, "search_web")
72
  builder.add_edge("title_generator", "content_generator")
73
+ builder.add_edge("search_web", "content_generator")
74
  builder.add_edge("content_generator", "content_reviewer")
75
  builder.add_edge("content_reviewer", "quality_check")
76
 
 
88
  - Attention-grabbing
89
  - Between 6-12 words"""
90
 
91
+ with st.status("๐Ÿš€ Generating Titles and Searching Web..."):
92
+ response = llm.invoke(prompt)
93
  state["title"] = response.content.split("\n")[0].strip('"')
94
  st.write(f"Selected title: **{state['title']}**")
95
  return state
96
 
97
+ def search_web(state: BlogState):
98
+
99
+ search_tool = TavilySearchResults(max_results=2)
100
+
101
+ # Create search query with date to get recent news
102
+ query = f"Latest data on {state["topic"]}"
103
+
104
+ # Execute search
105
+ search_results = search_tool.invoke({"query": query})
106
+
107
+ # Filter out YouTube results and non-English content
108
+ filtered_results = []
109
+ for result in search_results:
110
+ if "youtube.com" not in result.get("url", "").lower():
111
+ # Check if content is in English
112
+ content = result.get("content", "") + " " + result.get("title", "")
113
+ if is_english(content):
114
+ filtered_results.append(result)
115
+
116
+ st.write(f"Web Search Results: **{state['search_results']}**")
117
+
118
+ with st.status("๐Ÿš€ Searching Web..."):
119
+ st.write(f"Selected title: **{filtered_results}**")
120
+
121
+ return {
122
+ "search_results": [
123
+ {
124
+ "role": "system",
125
+ "content": f"{result['title']}\n{result['content']}\n(Source: {result['url']})"
126
+ }
127
+ for result in filtered_results
128
+ ]
129
+ }
130
+
131
+
132
  def generate_content(state: BlogState):
133
+ prompt = f"""Write a comprehensive blog post titled "{state["title"]}" and based on the web search results {state["search_results"]} with:
134
  1. Engaging introduction with hook
135
  2. 3-5 subheadings with detailed content
136
  3. Practical examples/statistics
 
139
  Style: Professional yet conversational (Flesch-Kincaid 60-70). Use markdown formatting"""
140
 
141
  with st.status("๐Ÿ“ Generating Content..."):
142
+ response = llm.invoke(prompt)
143
  state["blog_content"].append(AIMessage(content=response.content))
144
  st.markdown(response.content)
145
  return state
 
154
  Provide specific improvement suggestions. Content:\n{content}"""
155
 
156
  with st.status("๐Ÿ” Reviewing Content..."):
157
+ feedback = llm.invoke(prompt)
158
  state["reviewed_content"].append(HumanMessage(content=feedback.content))
159
  st.write(feedback.content)
160
  return state
 
169
  Answer only Pass or Fail:"""
170
 
171
  with st.status("โœ… Evaluating Quality..."):
172
+ response = llm.invoke(prompt)
173
  verdict = response.content.strip().upper()
174
  state["is_blog_ready"] = "Pass" if "PASS" in verdict else "Fail"
175
  state["reviewed_content"].append(AIMessage(
 
197
  api_key = st.text_input("Groq API Key:",
198
  type="password",
199
  value=os.getenv("GROQ_API_KEY", ""))
200
+
201
+ # Validate API key
202
  if not api_key:
203
  st.warning("โš ๏ธ Please enter your GROQ API key to proceed. Don't have? refer : https://console.groq.com/keys ")
204
+
205
+ # Groq API Key Input
206
+ tavily_api_key = os.environ["TAVILY_API_KEY"] = st.session_state["TAVILY_API_KEY"] = st.text_input("Tavily API Key:",
207
+ type="password",
208
+ value=os.getenv("TAVILY_API_KEY", ""))
209
 
210
+ # Validate API key
211
+ if not tavily_api_key:
212
+ st.warning("โš ๏ธ Please enter your TAVILY_API_KEY key to proceed. Don't have? refer : https://app.tavily.com/home")
213
+
214
  if st.button("Reset Session"):
215
  st.session_state.clear()
216
  st.rerun()
 
232
 
233
  # Initialize and run graph
234
  st.session_state.graph = init_graph(api_key)
235
+
236
  st.session_state.blog_state = BlogState(
237
  topic=topic,
238
  title="",
239
+ search_results=[],
240
  blog_content=[],
241
  reviewed_content=[],
242
  is_blog_ready=""
requirements.txt CHANGED
@@ -5,4 +5,5 @@ langchain_core
5
  langchain_groq
6
  langchain_openai
7
  faiss_cpu
8
- streamlit
 
 
5
  langchain_groq
6
  langchain_openai
7
  faiss_cpu
8
+ streamlit
9
+ langdetect