Spaces:
Sleeping
Sleeping
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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() |