parjun commited on
Commit
b7c0cee
·
verified ·
1 Parent(s): a438e91

Upload bloggenpart2.py

Browse files
Files changed (1) hide show
  1. bloggenpart2.py +284 -0
bloggenpart2.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Dict, List, Tuple, Any, Optional
3
+ from pydantic import BaseModel, Field
4
+ import streamlit as st
5
+ from dotenv import load_dotenv
6
+ from langchain_core.prompts import ChatPromptTemplate
7
+ from langchain_openai import ChatOpenAI
8
+ from langchain_groq import ChatGroq
9
+ from langgraph.graph import StateGraph, END
10
+
11
+ # Load environment variables (still useful as fallback)
12
+ load_dotenv()
13
+
14
+ # Configure page
15
+ st.set_page_config(page_title="AI Blog Generator", layout="wide")
16
+
17
+ # API Key handling in sidebar
18
+ with st.sidebar:
19
+ st.title("Configuration")
20
+
21
+ # LLM Provider Selection
22
+ provider = st.radio("LLM Provider", ["OpenAI", "Groq"])
23
+
24
+ if provider == "OpenAI":
25
+ openai_api_key = st.text_input("OpenAI API Key", type="password", help="Enter your OpenAI API key here")
26
+ model = st.selectbox("Model", ["gpt-3.5-turbo", "gpt-4", "gpt-4o"])
27
+
28
+ if openai_api_key:
29
+ os.environ["OPENAI_API_KEY"] = openai_api_key
30
+ else: # Groq
31
+ groq_api_key = st.text_input("Groq API Key", type="password", help="Enter your Groq API key here")
32
+ model = st.selectbox("Model", ["llama-3.3-70b-versatile","gemma2-9b-it","qwen-2.5-32b","mistral-saba-24b", "deepseek-r1-distill-qwen-32b"])
33
+
34
+ if groq_api_key:
35
+ os.environ["GROQ_API_KEY"] = groq_api_key
36
+
37
+ st.divider()
38
+ st.write("## About")
39
+ st.write("This app uses LangGraph to generate structured blog posts with a multi-step workflow.")
40
+ st.write("Made with ❤️ using LangGraph and Streamlit")
41
+
42
+ # Define the state schema
43
+ class BlogGeneratorState(BaseModel):
44
+ topic: str = Field(default="")
45
+ audience: str = Field(default="")
46
+ tone: str = Field(default="")
47
+ word_count: int = Field(default=500)
48
+ outline: List[str] = Field(default_factory=list)
49
+ sections: Dict[str, str] = Field(default_factory=dict)
50
+ final_blog: str = Field(default="")
51
+ error: Optional[str] = Field(default=None)
52
+
53
+ # Initialize LLM based on selected provider
54
+ def get_llm():
55
+ global provider, model
56
+
57
+ if provider == "OpenAI":
58
+ if not os.environ.get("OPENAI_API_KEY"):
59
+ st.error("Please enter your OpenAI API key in the sidebar")
60
+ st.stop()
61
+ return ChatOpenAI(model=model, temperature=0.7)
62
+ else: # Groq
63
+ if not os.environ.get("GROQ_API_KEY"):
64
+ st.error("Please enter your Groq API key in the sidebar")
65
+ st.stop()
66
+ return ChatGroq(model=model, temperature=0.7)
67
+
68
+ # Create prompt templates
69
+ outline_prompt = ChatPromptTemplate.from_template(
70
+ """You are a professional blog writer. Create an outline for a blog post about {topic}.
71
+ The audience is {audience} and the tone should be {tone}.
72
+ The blog should be approximately {word_count} words.
73
+
74
+ Return ONLY the outline as a list of section headings (without numbers or bullets).
75
+ Each heading should be concise and engaging."""
76
+ )
77
+
78
+ section_prompt = ChatPromptTemplate.from_template(
79
+ """Write content for the following section of a blog post about {topic}:
80
+
81
+ Section: {section}
82
+
83
+ The audience is {audience} and the tone should be {tone}.
84
+ Make this section approximately {section_word_count} words.
85
+ Make the content engaging, informative, and valuable to the reader.
86
+
87
+ Return ONLY the content for this section, without the heading."""
88
+ )
89
+
90
+ final_assembly_prompt = ChatPromptTemplate.from_template(
91
+ """You have a blog post with the following sections:
92
+
93
+ {sections_content}
94
+
95
+ Format this into a complete, professional blog post in Markdown format with:
96
+ 1. An engaging title at the top as an H1 heading
97
+ 2. A brief introduction before the first section
98
+ 3. Each section heading as an H2
99
+ 4. A conclusion at the end
100
+ 5. Proper spacing between sections
101
+ 6. 2-3 relevant markdown formatting elements like bold, italic, blockquotes, or bullet points where appropriate
102
+
103
+ The blog should maintain the {tone} tone and be targeted at {audience}.
104
+ Make it flow naturally between sections."""
105
+ )
106
+
107
+ # Define the nodes for the graph
108
+ def get_outline(state: BlogGeneratorState) -> BlogGeneratorState:
109
+ """Generate an outline for the blog post."""
110
+ try:
111
+ llm = get_llm()
112
+ chain = outline_prompt | llm
113
+ response = chain.invoke({
114
+ "topic": state.topic,
115
+ "audience": state.audience,
116
+ "tone": state.tone,
117
+ "word_count": state.word_count
118
+ })
119
+
120
+ # Parse the outline into a list
121
+ output_text = response.content
122
+ outline = [line.strip() for line in output_text.split('\n') if line.strip()]
123
+ return BlogGeneratorState(**{**state.model_dump(), "outline": outline})
124
+ except Exception as e:
125
+ st.error(f"Outline Error: {str(e)}")
126
+ return BlogGeneratorState(**{**state.model_dump(), "error": f"Error generating outline: {str(e)}"})
127
+
128
+ def generate_sections(state: BlogGeneratorState) -> BlogGeneratorState:
129
+ """Generate content for each section in the outline."""
130
+ if state.error:
131
+ return state
132
+
133
+ sections = {}
134
+ section_word_count = state.word_count // len(state.outline)
135
+
136
+ try:
137
+ llm = get_llm()
138
+ chain = section_prompt | llm
139
+
140
+ # Show progress
141
+ progress_bar = st.progress(0)
142
+ status_text = st.empty()
143
+
144
+ for i, section in enumerate(state.outline):
145
+ status_text.text(f"Generating section {i+1}/{len(state.outline)}: {section}")
146
+
147
+ response = chain.invoke({
148
+ "topic": state.topic,
149
+ "section": section,
150
+ "audience": state.audience,
151
+ "tone": state.tone,
152
+ "section_word_count": section_word_count
153
+ })
154
+
155
+ sections[section] = response.content
156
+ progress_bar.progress((i + 1) / len(state.outline))
157
+
158
+ status_text.empty()
159
+ progress_bar.empty()
160
+
161
+ return BlogGeneratorState(**{**state.model_dump(), "sections": sections})
162
+ except Exception as e:
163
+ return BlogGeneratorState(**{**state.model_dump(), "error": f"Error generating sections: {str(e)}"})
164
+
165
+ def assemble_blog(state: BlogGeneratorState) -> BlogGeneratorState:
166
+ """Assemble the final blog post in Markdown format."""
167
+ if state.error:
168
+ return state
169
+
170
+ try:
171
+ llm = get_llm()
172
+ chain = final_assembly_prompt | llm
173
+
174
+ sections_content = "\n\n".join([f"Section: {heading}\nContent: {content}"
175
+ for heading, content in state.sections.items()])
176
+
177
+ response = chain.invoke({
178
+ "sections_content": sections_content,
179
+ "tone": state.tone,
180
+ "audience": state.audience
181
+ })
182
+
183
+ final_blog = response.content
184
+ return BlogGeneratorState(**{**state.model_dump(), "final_blog": final_blog})
185
+ except Exception as e:
186
+ return BlogGeneratorState(**{**state.model_dump(), "error": f"Error assembling blog: {str(e)}"})
187
+
188
+ # Define the workflow graph
189
+ def create_blog_generator_graph():
190
+ workflow = StateGraph(BlogGeneratorState)
191
+
192
+ # Add nodes
193
+ workflow.add_node("get_outline", get_outline)
194
+ workflow.add_node("generate_sections", generate_sections)
195
+ workflow.add_node("assemble_blog", assemble_blog)
196
+
197
+ # Add edges
198
+ workflow.add_edge("get_outline", "generate_sections")
199
+ workflow.add_edge("generate_sections", "assemble_blog")
200
+ workflow.add_edge("assemble_blog", END)
201
+
202
+ # Set the entry point
203
+ workflow.set_entry_point("get_outline")
204
+
205
+ return workflow.compile()
206
+
207
+ # Create the Streamlit app main content
208
+ st.title("AI Blog Generator")
209
+ st.write("Generate professional blog posts with a structured workflow")
210
+
211
+ with st.form("blog_generator_form"):
212
+ topic = st.text_input("Blog Topic", placeholder="E.g., Sustainable Living in Urban Environments")
213
+
214
+ col1, col2 = st.columns(2)
215
+ with col1:
216
+ audience = st.text_input("Target Audience", placeholder="E.g., Young professionals")
217
+ tone = st.selectbox("Tone", ["Informative", "Conversational", "Professional", "Inspirational", "Technical"])
218
+
219
+ with col2:
220
+ word_count = st.slider("Approximate Word Count", min_value=300, max_value=2000, value=800, step=100)
221
+
222
+ submit_button = st.form_submit_button("Generate Blog")
223
+
224
+ if submit_button:
225
+ if (provider == "OpenAI" and not os.environ.get("OPENAI_API_KEY")) or \
226
+ (provider == "Groq" and not os.environ.get("GROQ_API_KEY")):
227
+ st.error(f"Please enter your {provider} API key in the sidebar before generating a blog")
228
+ elif not topic or not audience:
229
+ st.error("Please fill out all required fields.")
230
+ else:
231
+ with st.spinner(f"Initializing blog generation using {provider} {model}..."):
232
+ try:
233
+ # Initialize the graph
234
+ blog_generator = create_blog_generator_graph()
235
+
236
+ # Set the initial state
237
+ initial_state = BlogGeneratorState(
238
+ topic=topic,
239
+ audience=audience,
240
+ tone=tone,
241
+ word_count=word_count
242
+ )
243
+
244
+ # Run the graph
245
+ result = blog_generator.invoke(initial_state)
246
+
247
+ # Check if result is a dict and has expected keys
248
+ if isinstance(result, dict):
249
+ final_blog = result.get("final_blog", "")
250
+ outline = result.get("outline", [])
251
+ error = result.get("error")
252
+
253
+ if error:
254
+ st.error(f"Error: {error}")
255
+ elif final_blog:
256
+ # Display the blog post
257
+ st.success("Blog post generated successfully!")
258
+
259
+ st.subheader("Generated Blog Post")
260
+ st.markdown(final_blog)
261
+
262
+ # Download button for the blog post
263
+ st.download_button(
264
+ label="Download Blog as Markdown",
265
+ data=final_blog,
266
+ file_name=f"{topic.replace(' ', '_').lower()}_blog.md",
267
+ mime="text/markdown",
268
+ )
269
+
270
+ # Show metadata about the generation
271
+ st.info(f"Generated using {provider} {model}")
272
+
273
+ # Optionally show the outline
274
+ with st.expander("View Blog Outline"):
275
+ for i, section in enumerate(outline, 1):
276
+ st.write(f"{i}. {section}")
277
+ else:
278
+ st.error("Blog generation completed but no content was produced")
279
+ else:
280
+ st.error(f"Unexpected result type: {type(result)}")
281
+
282
+ except Exception as e:
283
+ st.error(f"An error occurred: {str(e)}")
284
+ st.info("Please check your API key and try again.")