BlogAssistant / app.py
neuralgeekroot's picture
sample check
22bd421
import os
import streamlit as st
from langchain_core.prompts import PromptTemplate
from langchain_groq import ChatGroq
from dotenv import load_dotenv
from langgraph.graph import StateGraph, START, END
from pydantic import BaseModel, Field
from typing import List, TypedDict, Annotated
from langgraph.constants import Send
import operator
from langchain_core.messages import SystemMessage, HumanMessage
from langsmith import traceable
from openai import OpenAI
# Load environment variables
load_dotenv()
os.environ['GROQ_API_KEY'] = os.getenv('GROQ_API_KEY')
os.environ['LANGCHAIN_API_KEY'] = os.getenv('LANGCHAIN_API_KEY')
os.environ['LANGSMITH_TRACING_V2'] = 'true'
os.environ['LANGCHAIN_PROJECT_NAME'] = os.getenv('LANGCHAIN_PROJECT_NAME')
os.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY')
# Initialize LLM
llm = ChatGroq(model='gemma2-9b-it')
# Initialize OpenAI client for DALL路E
client = OpenAI()
# Define section structure
class Section(BaseModel):
section_name: str = Field(description="Section name")
description: str = Field(description="Description of the section")
class Sections(BaseModel):
sections: List[Section] = Field(description="List of section details")
structured_sections = llm.with_structured_output(Sections)
# Define blog state
class BlogState(TypedDict):
topic: str
outline: str
sections: list[Section]
completed_section: Annotated[list, operator.add]
review_content: str
send_seo_optimization: str
revise_section_content: list[str]
finalize_blog: str
step: str
final_blog: str
image_urls: list
fallback_links: list
class BlogStateSection(TypedDict):
section: Section
completed_sections: Annotated[list, operator.add]
# Function to generate an image using DALL路E
def generate_image_with_dalle(prompt: str):
try:
response = client.images.generate(
model="dall-e-3",
prompt=prompt,
size="1024x1024",
quality="hd",
n=1,
)
image_url = response.data[0].url
return image_url
except Exception as e:
st.error(f"Failed to generate image: {e}")
return None
# Function to provide a fallback link for image search
def get_fallback_image_link(topic: str):
# Provide a Google Images search link for the topic
search_query = topic.replace(" ", "+")
return f"https://www.google.com/search?q={search_query}&tbm=isch"
# Orchestrator node to generate an outline
@traceable
def generate_outline(state: BlogState):
st.write("Generating an outline for the blog...")
result = structured_sections.invoke([
SystemMessage(content="Provide an interesting and informative content outline for the given {topic}."),
HumanMessage(content=f"Here is the blog topic: {state['topic']}")
])
return {'topic': state['topic'], 'outline': result.sections}
# Worker node to write sections
@traceable
def write_section(state: BlogStateSection):
st.write("Generating content for the section...")
section_content = llm.invoke([
SystemMessage(content="Write a detailed blog section based on the provided name and description."),
HumanMessage(content=f"Section Name: {state['section'].section_name}, Description: {state['section'].description}")
])
return {"completed_section": [section_content.content]}
# Function to generate an image using DALL路E
@traceable
def generate_image(state: BlogState):
st.write("Generating an image for the section...")
if not state.get('completed_section'):
st.warning("No completed sections found to generate an image.")
return {"image_urls": state.get('image_urls', []), "fallback_links": state.get('fallback_links', [])}
section = state['topic']
prompt = f"Generate an image for the blog section: {section} with no text. More of a representation and informative image"
image_url = generate_image_with_dalle(prompt)
if image_url:
image_urls = state.get('image_urls', [])
image_urls.append(image_url)
return {"image_urls": image_urls, "fallback_links": state.get('fallback_links', [])}
else:
fallback_links = state.get('fallback_links', [])
fallback_link = get_fallback_image_link(state['topic'])
fallback_links.append(fallback_link)
return {"image_urls": state.get('image_urls', []), "fallback_links": fallback_links}
# Review node to check the quality of sections
@traceable
def review_section(state: BlogState):
st.write("Reviewing the section...")
prompt = PromptTemplate.from_template(
"Check if the section can be improved: {completed_section}. "
"If no, return 'send_seo_optimization'. "
"If yes, return 'revise_section_content'."
)
chain = prompt | llm
result = chain.invoke({'completed_section': state['completed_section']})
decision = result.content.strip().lower()
if decision not in ["send_seo_optimization", "revise_section_content"]:
decision = "send_seo_optimization"
return {"step": decision}
# Revision node to improve content
@traceable
def revise_section(state: BlogState):
st.write("Revising the section content...")
if state['step'] == "revise_section_content":
if not state.get('sections'):
st.warning("No sections found to revise.")
return {"completed_section": state['completed_section']}
revised_content = llm.invoke([
SystemMessage(content="Based on the review feedback, improve the content further."),
HumanMessage(content=f"Section Name: {state['sections'][0].section_name}, Description: {state['sections'][0].description}")
])
return {"completed_section": [revised_content.content]}
# Assign writers dynamically to sections
@traceable
def assign_writers(state: BlogState):
st.write("Assigning writers to sections...")
if not state.get('outline'):
st.warning("No outline found to assign writers.")
return []
return [Send('write_section', {'section': s}) for s in state['outline']]
# Decision function for routing after review
def should_revise(state: BlogState):
return state["step"]
# SEO Optimization step
@traceable
def seo_optimization(state: BlogState):
st.write("Performing SEO optimization...")
result = llm.invoke(f"Optimize the blog for search ranking: {state['topic']}")
return {'finalize_blog': result.content}
# Final publishing step
@traceable
def publish_blog(state: BlogState):
st.write("Finalizing and publishing the blog...")
final_blog = state['finalize_blog']
# Add images to the blog
if state.get('image_urls'):
st.subheader("AI-Generated Images")
for image_url in state['image_urls']:
st.image(image_url, caption="AI-Generated Image")
# Add fallback links if images were not generated
if state.get('fallback_links'):
st.subheader("Fallback Image Search Links")
for link in state['fallback_links']:
st.markdown(f"[Search for related images on Google]({link})")
return {"final_blog": final_blog}
# Build LangGraph workflow
builder = StateGraph(BlogState)
# Add orchestrator nodes
builder.add_node('generate_outline', generate_outline)
# Add worker and review nodes
builder.add_node('write_section', write_section)
builder.add_node('generate_image', generate_image)
builder.add_node('review_section', review_section)
builder.add_node('revise_section', revise_section)
# Add finalization nodes
builder.add_node('seo_optimization', seo_optimization)
builder.add_node('publish_blog', publish_blog)
# Define workflow edges
builder.add_edge(START, 'generate_outline')
builder.add_conditional_edges('generate_outline', assign_writers, ['write_section'])
builder.add_edge('write_section', 'generate_image')
builder.add_edge('generate_image', 'review_section')
builder.add_conditional_edges('review_section', should_revise, {'revise_section_content': 'revise_section', 'send_seo_optimization': 'seo_optimization'})
builder.add_edge('revise_section', 'review_section') # Loop back after revision
builder.add_edge('seo_optimization', 'publish_blog')
builder.add_edge('publish_blog', END)
# Compile workflow
workflow = builder.compile()
# Streamlit app
def main():
st.title("Blog Writing Assistant")
# Input for blog topic
topic = st.text_input("Enter the blog topic:")
if st.button("Generate Blog"):
if topic:
# Define initial state
initial_state = {
'topic': topic,
'outline': "",
'sections': [],
'completed_section': [],
'review_content': "",
'send_seo_optimization': "",
'revise_section_content': [],
'finalize_blog': "",
'step': "",
'final_blog': "",
'image_urls': [], # Initialize image URLs list
'fallback_links': [] # Initialize fallback links list
}
# Invoke workflow
try:
result = workflow.invoke(initial_state)
st.subheader("Final Blog Content")
st.write(result['final_blog'])
except Exception as e:
st.error(f"An error occurred: {e}")
else:
st.error("Please enter a blog topic.")
if __name__ == "__main__":
main()