├── .env.example ├── README.md ├── examples ├── perspective │ ├── agents.md │ └── template.txt └── product_update │ ├── langgraph_update.md │ └── template.txt ├── langgraph.json ├── pyproject.toml └── src └── agent ├── __init__.py ├── configuration.py ├── graph.py ├── notes ├── agents.txt └── langgraph_cloud.txt ├── prompts.py ├── state.py └── utils.py /.env.example: -------------------------------------------------------------------------------- 1 | ANTHROPIC_API_KEY= -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Robo Blogger 2 | 3 | Creating polished blog posts is traditionally time-consuming and challenging. The gap between having great ideas and turning them into well-structured content can be significant. Robo Blogger addresses this challenge by transforming the content creation process. The key insight is that our best ideas often come when we're away from the keyboard - while walking, commuting, or right after a meeting. Robo Blogger leverages this by: 4 | 5 | 1. **Capturing Ideas Naturally**: Instead of starting with writing, [simply speak your thoughts using any voice-to-text app](https://hamel.dev/blog/posts/audience/#build-a-voice-to-content-pipeline) 6 | 2. **Maintaining Structure**: Convert raw ideas into polished content while following proven blog post patterns 7 | 3. **Grounding in Documentation**: Optionally incorporate reference materials to ensure accuracy and depth 8 | 9 | The workflow is streamlined to three steps: 10 | 1. **Voice Capture**: Record your thoughts using any dictation app (e.g., Flowvoice) 11 | 2. **Planning**: Claude 3.5 Sonnet converts your dictation and structure into a coherent plan 12 | 3. **Writing**: Automated generation of each blog section following the plan, using your dictation and any documentation links 13 | 14 | This approach builds on concepts from our previous [Report mAIstro](https://github.com/langchain-ai/report-mAIstro) project, but specifically optimized for blog post creation. By separating idea capture from content structuring, Robo Blogger helps maintain the authenticity of your original thoughts while ensuring professional presentation. 15 | 16 | ![robo-blogger](https://github.com/user-attachments/assets/0599ebc3-bcd7-4a1f-abe5-07ee4e828ec8) 17 | 18 | ## Quickstart 19 | 20 | Set API keys for the LLM of choice (default is Anthropic Claude 3.5 Sonnet): 21 | ``` 22 | cp .env.example .env 23 | ``` 24 | 25 | Clone the repository and launch the assistant [using the LangGraph server](https://langchain-ai.github.io/langgraph/cloud/reference/cli/#dev): 26 | ```bash 27 | curl -LsSf https://astral.sh/uv/install.sh | sh 28 | git clone https://github.com/langchain-ai/robo-blogger.git 29 | cd robo-blogger 30 | uvx --refresh --from "langgraph-cli[inmem]" --with-editable . --python 3.11 langgraph dev 31 | ``` 32 | 33 | This will open LangGraph Studio in your browser. 34 | 35 | ### Required Input 36 | 37 | The only required input is the name of the audio dictation file (e.g., `audio_dictation.txt` in `notes` folder). You can use any audio-to-voice dictation app (e.g., [Flowvoice](https://www.flowvoice.ai/)) to create this file. 38 | ``` 39 | notes/audio_dictation.txt 40 | ``` 41 | 42 | ![Screenshot 2024-12-16 at 4 53 37 PM](https://github.com/user-attachments/assets/de7acd1f-9ee3-49f5-8aef-26bcda8ae479) 43 | 44 | ### Optional Inputs 45 | 46 | Two additional inputs are optional: 47 | 1. A list of URLs to documentation that you want to use to help write the blog post. 48 | 2. A template for the blog post structure. 49 | 50 | ![Screenshot 2024-12-16 at 3 20 10 PM](https://github.com/user-attachments/assets/8903f08c-eba0-4abc-b5a6-8bd3eff8fe9a) 51 | 52 | In the `configuration` tab, you can provide template for the blog post structure (see ## Customization below for examples). 53 | 54 | ![Screenshot 2024-12-16 at 4 45 12 PM](https://github.com/user-attachments/assets/1712c440-68c0-4655-bd5f-8078fbfa125e) 55 | 56 | ## Customization 57 | 58 | We've found that blog posts typically follow a consistent structure. For example, we have: 59 | 60 | * Product update: https://blog.langchain.dev/langgraph-cloud/ 61 | * Perspective: https://blog.langchain.dev/what-is-an-agent/ 62 | 63 | Templates for different types of blog posts can be passed in as a configuration option. 64 | 65 | ### Product Update Example 66 | 67 | URLs provided: 68 | * "https://langchain-ai.github.io/langgraph/concepts/", 69 | * "https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/", 70 | * "https://langchain-ai.github.io/langgraph/concepts/deployment_options/" 71 | 72 | Blog structure provided: 73 | * [`examples/product_update/template.txt`](examples/product_update/template.txt) 74 | 75 | Audio dictation provided: 76 | * [`notes/langgraph_cloud.txt`](notes/langgraph_cloud.txt) 77 | 78 | Resulting blog post: 79 | * [`examples/product_update/langgraph_update.md`](examples/product_update/langgraph_update.md) 80 | 81 | ### Perspective Example 82 | 83 | URLs provided: 84 | * "https://langchain-ai.github.io/langgraph/concepts/high_level/", 85 | * "https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/", 86 | * "https://www.deeplearning.ai/the-batch/issue-253/" 87 | 88 | Blog structure provided: 89 | * [`examples/perspective/template.txt`](examples/perspective/template.txt) 90 | 91 | Audio dictation provided: 92 | * [`notes/agents.txt`](notes/agents.txt) 93 | 94 | Resulting blog post: 95 | * [`examples/perspective/agents.md`](examples/perspective/agents.md) 96 | -------------------------------------------------------------------------------- /examples/perspective/agents.md: -------------------------------------------------------------------------------- 1 | # The AI Agent Conundrum: Navigating the Spectrum of Capabilities 2 | 3 | In the rapidly evolving landscape of artificial intelligence, the concept of AI agents has become a focal point of intense debate and exploration. For developers and researchers working with frameworks like Langchain, understanding the nuances of agent capabilities is not just academic—it's a practical necessity. The industry grapples with a fundamental question: What exactly constitutes an AI agent? This seemingly straightforward inquiry opens up a Pandora's box of complexities, revealing a spectrum of functionalities rather than a binary classification. As we delve into this topic, we'll examine the current discourse surrounding AI agents, their relevance to Langchain users, and propose a nuanced perspective that views agent capabilities as a continuum rather than discrete categories. This approach aims to provide a more accurate and useful framework for conceptualizing and implementing AI agents in real-world applications. 4 | 5 | ## Unraveling the AI Agent: A Spectrum of Capabilities 6 | 7 | An AI agent can be broadly defined as a system that uses a large language model (LLM) to determine the control flow of an application. However, rather than a binary classification, it's more useful to consider agents on a spectrum of capabilities and autonomy. 8 | 9 | At the simplest level, an agent may function as a router, allowing an LLM to choose between a few specific steps. As complexity increases, agents can incorporate looping behavior, deciding whether to continue iterating or complete a task. The most advanced agents, exemplified by systems like those in the Voyager paper, can actually construct their own tools. 10 | 11 | This spectrum of agent capabilities parallels the levels of autonomy seen in self-driving cars. Just as vehicles range from basic driver assistance to full autonomy, AI agents span from simple decision-making to complex, self-directed problem-solving. 12 | 13 | The more agentic a system becomes, the more critical orchestration frameworks become. Increased complexity introduces more opportunities for errors, which can result in wasted computational resources or increased latency. Frameworks like LangGraph offer built-in support for features that enable more sophisticated agents, including: 14 | 15 | - Human-in-the-loop interactions 16 | - Short-term and long-term memory management 17 | - Streaming capabilities 18 | 19 | These tools help developers manage the increased complexity of highly agentic systems, improving reliability and performance as agents take on more autonomous and complex tasks. 20 | 21 | MISSING INFORMATION: The provided sources do not contain specific details about the Voyager paper or concrete examples of how LangGraph implements the mentioned features. Additional sources would be needed to provide more detailed information on these topics. 22 | 23 | ## Conclusion: Navigating the Frontier of AI Agent Orchestration 24 | 25 | As AI agents grow in complexity and capability, the need for sophisticated orchestration frameworks becomes increasingly critical. Tools like LangGraph and Langsmith are at the forefront of this evolution, providing developers with the means to design, manage, and optimize intricate AI systems. These frameworks not only facilitate the creation of more powerful and flexible AI agents but also address the challenges of coordination, state management, and scalability. 26 | 27 | The future of AI agents lies in their ability to work in concert, tackling complex tasks through distributed cognition and coordinated action. As researchers and developers, we must grapple with the implications of these advancements, considering both the technical challenges and the ethical considerations they present. The field of AI agent orchestration is ripe with opportunities for innovation, promising to unlock new paradigms in artificial intelligence and human-machine collaboration. 28 | 29 | As we stand on the cusp of this new era, it is incumbent upon us to approach these developments with rigor, creativity, and a commitment to responsible AI practices. The tools and frameworks we create today will shape the AI landscape of tomorrow, making it essential that we continue to push the boundaries of what's possible while ensuring the systems we build are robust, transparent, and aligned with human values. -------------------------------------------------------------------------------- /examples/perspective/template.txt: -------------------------------------------------------------------------------- 1 | The blog post should follow this structured format for thought leadership: 2 | 3 | 1. Opening Hook & Context (1 section) 4 | - Start with a compelling question or observation 5 | - Establish relevance to current industry discourse 6 | - Present author's unique perspective/thesis 7 | - Maximum 150 words 8 | 9 | 2. Core Concept Definition (1 section) 10 | - Clear definition of the main concept 11 | - Acknowledgment of definition challenges 12 | - Author's technical perspective 13 | - Alternative viewpoints or industry context 14 | - Maximum 200 words 15 | 16 | 3. Future Outlook (1 section) 17 | - Industry implications 18 | - Future challenges or opportunities 19 | - Call to action or next steps 20 | - Maximum 150 words 21 | 22 | Style Guidelines: 23 | - Use conversational, authoritative tone 24 | - Include 💡 for key insights 25 | - Include relevant examples throughout 26 | - Balance technical depth with accessibility 27 | - Connect to broader industry trends 28 | - Encourage discussion and debate -------------------------------------------------------------------------------- /examples/product_update/langgraph_update.md: -------------------------------------------------------------------------------- 1 | # Langraph Cloud: Streamlined Deployment for Complex AI Workflows 2 | 3 | Langraph, a powerful framework for orchestrating multi-agent AI systems, has revolutionized the development of complex language model applications. Building upon this foundation, Langraph Cloud emerges as a robust deployment solution, designed to simplify the process of bringing sophisticated AI workflows to production environments. This cloud-based platform offers developers a seamless way to deploy, manage, and scale their Langraph applications, eliminating many of the operational complexities associated with running advanced AI systems. By providing a dedicated infrastructure tailored for Langraph's unique requirements, Langraph Cloud enables teams to focus on innovation rather than infrastructure management, accelerating the path from concept to production-ready AI solutions. 4 | 5 | ## Product Context & Evolution 6 | 7 | LangGraph emerged as a framework to address the challenges of building complex AI applications using large language models (LLMs). It introduced a graph-based approach for constructing agent workflows, allowing developers to represent different components as nodes and information flow as edges. 8 | 9 | As LangGraph gained adoption, the need for robust deployment solutions became apparent. Many applications required features like streaming support, background processing, and persistent memory - capabilities that were complex to implement manually. This led to the development of LangGraph Platform, a commercial solution built on top of the open-source LangGraph framework. 10 | 11 | LangGraph Platform offers several deployment options to meet diverse needs: 12 | 13 | - Self-Hosted Lite: A free, limited version for local or self-hosted deployment 14 | - Cloud SaaS: Hosted as part of LangSmith for easy management 15 | - Bring Your Own Cloud: Managed infrastructure within the user's cloud environment 16 | - Self-Hosted Enterprise: Full control and management by the user 17 | 18 | This evolution from LangGraph to LangGraph Platform reflects the growing sophistication of LLM-based applications and the need for production-ready deployment solutions that handle complex scenarios like long-running processes, bursty loads, and human-in-the-loop interactions. 19 | 20 | MISSING INFORMATION: Specific dates or timeline for the evolution from LangGraph to LangGraph Platform. 21 | 22 | ## Core Features of Langraph Cloud 23 | 24 | Langraph Cloud offers several powerful capabilities for deploying and managing agent-based applications at scale: 25 | 26 | **Integrated Monitoring**: Langraph Cloud provides robust monitoring tools to track agent performance, execution flows, and system health. This allows developers to gain insights into their deployed applications and troubleshoot issues efficiently. 27 | 28 | **Double Texting Handling**: A common challenge in LLM applications is managing rapid successive user inputs. Langraph Cloud implements strategies to gracefully handle "double texting" scenarios, ensuring smooth user interactions even during high-frequency inputs. 29 | 30 | **Cron Jobs**: Langraph Cloud supports scheduled tasks through cron jobs, enabling periodic execution of agent workflows or maintenance tasks without manual intervention. 31 | 32 | **Background Processing**: For long-running agent tasks, Langraph Cloud offers background processing capabilities. This allows agents to execute complex operations asynchronously, with polling endpoints and webhooks for status updates. 33 | 34 | **Streaming Support**: Langraph Cloud enables streaming of both token outputs and intermediate states back to users, providing real-time feedback during agent execution and improving the overall user experience. 35 | 36 | **Persistence and Memory Management**: Built-in checkpointers and optimized memory stores allow agents to maintain state across sessions, crucial for applications requiring conversation history or evolving context. 37 | 38 | **Human-in-the-Loop Integration**: Langraph Cloud provides specialized endpoints to facilitate human intervention in agent workflows, enabling oversight and manual input when needed. 39 | 40 | These features collectively address many of the challenges developers face when scaling and deploying complex agent-based applications, allowing teams to focus on refining agent logic rather than infrastructure concerns. 41 | 42 | [MISSING INFORMATION: Customer testimonials are not provided in the source material.] 43 | 44 | ## Implementation Details: Getting Started with Langraph Cloud 45 | 46 | To begin using Langraph Cloud, you'll need to set up your application structure and configure the necessary components. A typical Langraph Cloud application consists of one or more graphs, a Langraph API Configuration file (langgraph.json), dependency specifications, and environment variables. 47 | 48 | Start by defining your graph structure using the Langraph framework. Then, create a `langgraph.json` file to specify your API configuration: 49 | 50 | ```json 51 | { 52 | "graphs": { 53 | "main": { 54 | "path": "path/to/your/graph.py", 55 | "class_name": "YourGraphClass" 56 | } 57 | }, 58 | "dependencies": { 59 | "python": "requirements.txt" 60 | } 61 | } 62 | ``` 63 | 64 | Next, set up your deployment environment. Langraph Cloud offers multiple deployment options: 65 | 66 | 1. Self-Hosted Lite: Free up to 1 million nodes executed, suitable for local or self-hosted deployments. 67 | 2. Cloud SaaS: Hosted as part of LangSmith, available for Plus and Enterprise plans. 68 | 3. Bring Your Own Cloud: Managed infrastructure within your AWS cloud (Enterprise plan only). 69 | 4. Self-Hosted Enterprise: Fully managed by you (Enterprise plan only). 70 | 71 | Choose the option that best fits your needs and scale. For Cloud SaaS deployments, you can integrate with GitHub to deploy code directly from your repositories. 72 | 73 | Langraph Cloud provides built-in features like streaming support, background runs, and double texting handling. It also includes optimized checkpointers and memory management for persistent applications. 74 | 75 | MISSING INFORMATION: Specific steps for deploying to Langraph Cloud and any required API keys or authentication processes. 76 | 77 | ## Conclusion: Harness the Power of Langraph Cloud Today 78 | 79 | Langraph Cloud offers a robust platform for deploying and managing complex AI workflows with ease. By leveraging its serverless architecture, developers can focus on building sophisticated language models and graph-based applications without the overhead of infrastructure management. The platform's seamless integration with LangChain components, coupled with its scalable and cost-effective deployment options, makes it an ideal choice for both small-scale projects and enterprise-level implementations. To explore Langraph Cloud's capabilities firsthand, visit the official documentation for concepts (https://langchain-ai.github.io/langgraph/concepts/), platform details (https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/), and deployment options (https://langchain-ai.github.io/langgraph/concepts/deployment_options/). Start optimizing your AI workflows with Langraph Cloud today. -------------------------------------------------------------------------------- /examples/product_update/template.txt: -------------------------------------------------------------------------------- 1 | The blog post should follow this structured format for product updates: 2 | 3 | 1. Executive Summary (max 1 section) 4 | - Brief context/background of the product/feature 5 | - Clear statement of what's being announced 6 | - Key value proposition 7 | - Maximum 150 words 8 | 9 | 2. Product Context & Evolution (1 section) 10 | - Historical context or problem being solved 11 | - Journey to current solution 12 | - Why this update matters 13 | - Maximum 200 words 14 | 15 | 3. Core Announcement (1 section) 16 | - Section that outlines the feature/update, including: 17 | * Detailed feature description 18 | * Technical capabilities 19 | * Real-world use cases 20 | * Customer testimonials (if available, otherwise leave placeholder) 21 | - Maximum 250 words 22 | 23 | 4. Implementation Details (1 section) 24 | - How to get started 25 | - Technical requirements 26 | - Integration steps 27 | - Code examples or configuration details 28 | - Maximum 250 words 29 | 30 | 5. Closing (1 section) 31 | - Summary of benefits 32 | - Future roadmap hints (optional) 33 | - Clear call to action 34 | - Additional resources and links 35 | - Contact information 36 | - Maximum 150 words 37 | 38 | Style Guidelines: 39 | - Use ### for section headers 40 | - Include EXACTLY 5 sections, as shown above 41 | - Leave placeholders for customer quotes in blockquotes 42 | - Start with any important updates/edits at the top 43 | - Include relevant links in the closing section 44 | - Use bullet points for feature lists -------------------------------------------------------------------------------- /langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dockerfile_lines": [], 3 | "graphs": { 4 | "robo_blogger": "./src/agent/graph.py:graph" 5 | }, 6 | "python_version": "3.11", 7 | "env": "./.env", 8 | "dependencies": [ 9 | "." 10 | ] 11 | } -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "robo-blogger" 3 | version = "0.0.1" 4 | description = "Assistant for creating full blog posts from an audio dictation." 5 | authors = [ 6 | { name = "Lance Martin" } 7 | ] 8 | readme = "README.md" 9 | license = { text = "MIT" } 10 | requires-python = ">=3.9" 11 | dependencies = [ 12 | "langgraph>=0.2.55", 13 | "langchain-community>=0.3.9", 14 | "langchain-anthropic>=0.3.0", 15 | "beautifulsoup4>=4.12.2", 16 | ] 17 | 18 | [project.optional-dependencies] 19 | dev = ["mypy>=1.11.1", "ruff>=0.6.1"] 20 | 21 | [build-system] 22 | requires = ["setuptools>=73.0.0", "wheel"] 23 | build-backend = "setuptools.build_meta" 24 | 25 | [tool.setuptools] 26 | packages = ["agent"] 27 | 28 | [tool.setuptools.package-dir] 29 | "agent" = "src/agent" 30 | 31 | [tool.setuptools.package-data] 32 | "*" = ["py.typed"] 33 | 34 | [tool.ruff] 35 | lint.select = [ 36 | "E", # pycodestyle 37 | "F", # pyflakes 38 | "I", # isort 39 | "D", # pydocstyle 40 | "D401", # First line should be in imperative mood 41 | "T201", 42 | "UP", 43 | ] 44 | lint.ignore = [ 45 | "UP006", 46 | "UP007", 47 | "UP035", 48 | "D417", 49 | "E501", 50 | ] 51 | 52 | [tool.ruff.lint.per-file-ignores] 53 | "tests/*" = ["D", "UP"] 54 | 55 | [tool.ruff.lint.pydocstyle] 56 | convention = "google" -------------------------------------------------------------------------------- /src/agent/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langchain-ai/robo-blogger/7b827a294e9e8d71d2250f374a81c1b2c3c35aab/src/agent/__init__.py -------------------------------------------------------------------------------- /src/agent/configuration.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dataclasses import dataclass, fields 3 | from typing import Any, Optional 4 | 5 | from langchain_core.runnables import RunnableConfig 6 | from dataclasses import dataclass 7 | 8 | DEFAULT_BLOG_STRUCTURE = """The blog post should follow this strict three-part structure: 9 | 10 | 1. Introduction (max 1 section) 11 | - Start with ### Key Links and include user-provided links 12 | - Brief overview of the problem statement 13 | - Brief overview of the solution/main topic 14 | - Maximum 100 words 15 | 16 | 2. Main Body (exactly 2-3 sections) 17 | - Each section must: 18 | * Cover a distinct aspect of the main topic 19 | * Include at least one relevant code snippet 20 | * Be 150-200 words 21 | - No overlap between sections 22 | 23 | 3. Conclusion (max 1 section) 24 | - Brief summary of key points 25 | - Key Links 26 | - Clear call to action 27 | - Maximum 150 words""" 28 | 29 | @dataclass(kw_only=True) 30 | class Configuration: 31 | """The configurable fields for the chatbot.""" 32 | blog_structure: str = DEFAULT_BLOG_STRUCTURE 33 | 34 | @classmethod 35 | def from_runnable_config( 36 | cls, config: Optional[RunnableConfig] = None 37 | ) -> "Configuration": 38 | """Create a Configuration instance from a RunnableConfig.""" 39 | configurable = ( 40 | config["configurable"] if config and "configurable" in config else {} 41 | ) 42 | values: dict[str, Any] = { 43 | f.name: os.environ.get(f.name.upper(), configurable.get(f.name)) 44 | for f in fields(cls) 45 | if f.init 46 | } 47 | return cls(**{k: v for k, v in values.items() if v}) -------------------------------------------------------------------------------- /src/agent/graph.py: -------------------------------------------------------------------------------- 1 | from langchain_anthropic import ChatAnthropic 2 | from langchain_core.messages import HumanMessage, SystemMessage 3 | from langchain_core.runnables import RunnableConfig 4 | 5 | from langgraph.constants import Send 6 | from langgraph.graph import START, END, StateGraph 7 | 8 | import agent.configuration as configuration 9 | from agent.state import Sections, BlogState, BlogStateInput, BlogStateOutput, SectionState 10 | from agent.prompts import blog_planner_instructions, main_body_section_writer_instructions, intro_conclusion_instructions 11 | from agent.utils import load_and_format_urls, read_dictation_file, format_sections 12 | 13 | # ------------------------------------------------------------ 14 | # LLMs 15 | claude_3_5_sonnet = ChatAnthropic(model="claude-3-5-sonnet-latest", temperature=0) 16 | 17 | # ------------------------------------------------------------ 18 | # Graph 19 | def generate_blog_plan(state: BlogState, config: RunnableConfig): 20 | """ Generate the report plan """ 21 | 22 | # Read transcribed notes 23 | user_instructions = read_dictation_file(state.transcribed_notes_file) 24 | 25 | # Get configuration 26 | configurable = configuration.Configuration.from_runnable_config(config) 27 | blog_structure = configurable.blog_structure 28 | 29 | # Format system instructions 30 | system_instructions_sections = blog_planner_instructions.format(user_instructions=user_instructions, blog_structure=blog_structure) 31 | 32 | # Generate sections 33 | structured_llm = claude_3_5_sonnet.with_structured_output(Sections) 34 | report_sections = structured_llm.invoke([SystemMessage(content=system_instructions_sections)]+[HumanMessage(content="Generate the sections of the blog. Your response must include a 'sections' field containing a list of sections. Each section must have: name, description, and content fields.")]) 35 | 36 | return {"sections": report_sections.sections} 37 | 38 | def write_section(state: SectionState): 39 | """ Write a section of the report """ 40 | 41 | # Get state 42 | section = state.section 43 | urls = state.urls 44 | 45 | # Read transcribed notes 46 | user_instructions = read_dictation_file(state.transcribed_notes_file) 47 | 48 | # Load and format urls 49 | url_source_str = "" if not urls else load_and_format_urls(urls) 50 | 51 | # Format system instructions 52 | system_instructions = main_body_section_writer_instructions.format(section_name=section.name, 53 | section_topic=section.description, 54 | user_instructions=user_instructions, 55 | source_urls=url_source_str) 56 | 57 | # Generate section 58 | section_content = claude_3_5_sonnet.invoke([SystemMessage(content=system_instructions)]+[HumanMessage(content="Generate a blog section based on the provided information.")]) 59 | 60 | # Write content to the section object 61 | section.content = section_content.content 62 | 63 | # Write the updated section to completed sections 64 | return {"completed_sections": [section]} 65 | 66 | def write_final_sections(state: SectionState): 67 | """ Write final sections of the report, which do not require web search and use the completed sections as context """ 68 | 69 | # Get state 70 | section = state.section 71 | urls = state.urls 72 | 73 | # Format system instructions 74 | system_instructions = intro_conclusion_instructions.format(section_name=section.name, 75 | section_topic=section.description, 76 | main_body_sections=state.blog_main_body_sections, 77 | source_urls="" if not urls else load_and_format_urls(urls)) 78 | 79 | # Generate section 80 | section_content = claude_3_5_sonnet.invoke([SystemMessage(content=system_instructions)]+[HumanMessage(content="Generate an intro/conclusion section based on the provided main body sections.")]) 81 | 82 | # Write content to section 83 | section.content = section_content.content 84 | 85 | # Write the updated section to completed sections 86 | return {"completed_sections": [section]} 87 | 88 | def initiate_section_writing(state: BlogState): 89 | """ This is the "map" step when we kick off web research for some sections of the report """ 90 | 91 | # Kick off section writing in parallel via Send() API for any sections that require research 92 | return [ 93 | Send("write_section", SectionState( 94 | section=s, 95 | transcribed_notes_file=state.transcribed_notes_file, 96 | urls=state.urls, 97 | completed_sections=[] # Initialize with empty list 98 | )) 99 | for s in state.sections 100 | if s.main_body 101 | ] 102 | 103 | def gather_completed_sections(state: BlogState): 104 | """ Gather completed main body sections""" 105 | 106 | # List of completed sections 107 | completed_sections = state.completed_sections 108 | 109 | # Format completed section to str to use as context for final sections 110 | completed_report_sections = format_sections(completed_sections) 111 | return {"blog_main_body_sections": completed_report_sections} 112 | 113 | def initiate_final_section_writing(state: BlogState): 114 | """ This is the "map" step when we kick off research on any sections that require it using the Send API """ 115 | 116 | # Kick off section writing in parallel via Send() API for any sections that do not require research 117 | return [ 118 | Send("write_final_sections", SectionState( 119 | section=s, 120 | blog_main_body_sections=state.blog_main_body_sections, 121 | urls=state.urls, 122 | completed_sections=[] # Initialize with empty list 123 | )) 124 | for s in state.sections 125 | if not s.main_body 126 | ] 127 | 128 | def compile_final_blog(state: BlogState): 129 | """ Compile the final blog """ 130 | 131 | # Get sections 132 | sections = state.sections 133 | completed_sections = {s.name: s.content for s in state.completed_sections} 134 | 135 | # Update sections with completed content while maintaining original order 136 | for section in sections: 137 | section.content = completed_sections[section.name] 138 | 139 | # Compile final report 140 | all_sections = "\n\n".join([s.content for s in sections]) 141 | 142 | return {"final_blog": all_sections} 143 | 144 | # Add nodes and edges 145 | builder = StateGraph(BlogState, input=BlogStateInput, output=BlogStateOutput, config_schema=configuration.Configuration) 146 | builder.add_node("generate_blog_plan", generate_blog_plan) 147 | builder.add_node("write_section", write_section) 148 | builder.add_node("compile_final_blog", compile_final_blog) 149 | builder.add_node("gather_completed_sections", gather_completed_sections) 150 | builder.add_node("write_final_sections", write_final_sections) 151 | builder.add_edge(START, "generate_blog_plan") 152 | builder.add_conditional_edges("generate_blog_plan", initiate_section_writing, ["write_section"]) 153 | builder.add_edge("write_section", "gather_completed_sections") 154 | builder.add_conditional_edges("gather_completed_sections", initiate_final_section_writing, ["write_final_sections"]) 155 | builder.add_edge("write_final_sections", "compile_final_blog") 156 | builder.add_edge("compile_final_blog", END) 157 | 158 | graph = builder.compile() 159 | -------------------------------------------------------------------------------- /src/agent/notes/agents.txt: -------------------------------------------------------------------------------- 1 | so I want to write a blog post about agents 2 | 3 | we get this question all the time at Langchain 4 | 5 | first the question is what actually is an agent? 6 | 7 | one simple definition is that an agent uses an LLM to determine the control flow of an application 8 | 9 | but, there's a spectrum of agents, kind of of like levels of autonomy with self-driving cars 10 | 11 | Andrew Ng had a good tweet on this where he suggested that rather than arguing over which word to include or exclude as being a true AI agent we can acknowledge that there are just different degrees to which systems can be agentic 12 | 13 | in the simple case you can think about a router, where the agent chooses between a new specific steps 14 | 15 | as we get more complex, an agent can have looping where it can decide to continue a loop or break out of it to finish the task 16 | 17 | the most advanced case, as we see in the Voyager paper, is an agent that actually builds its own tools 18 | 19 | now, the more agentic your system is the more an orchestration framework can help 20 | 21 | this is because there is more surface area for mistakes, which can cost tokens or latency 22 | 23 | frameworks like LangGraph can help because there is built-in support for features to support building more complex agents, such as human in the loop, short/long-term memory, and streaming 24 | 25 | similarly, frameworks like Langsmith can help with testing and observability for complex agents 26 | 27 | -------------------------------------------------------------------------------- /src/agent/notes/langgraph_cloud.txt: -------------------------------------------------------------------------------- 1 | We're going to write a blog post on Langraph Cloud. Start with general context on what Langraph is and use this to motivate Langraph Cloud with the need to deploy agents at scale. Then lay out the main features of Langraph Cloud like integrated monitoring, double texting, crons. Then go in more depth on implementation details that show how to get started with Langraph Cloud. Then close with a summary and a call to action to try it for yourself and provide all the relevant links. -------------------------------------------------------------------------------- /src/agent/prompts.py: -------------------------------------------------------------------------------- 1 | blog_planner_instructions="""You are an expert technical writer, helping to plan a blog post. 2 | 3 | Your goal is to generate a CONCISE outline. 4 | 5 | First, carefully reflect on these notes from the user about the scope of the blog post: 6 | {user_instructions} 7 | 8 | Next, structure these notes into a set of sections that follow the structure EXACTLY as shown below: 9 | {blog_structure} 10 | 11 | For each section, be sure to provide: 12 | - Name - Clear and descriptive section name 13 | - Description - An overview of specific topics to be covered each section, whether to include code examples, and the word count 14 | - Content - Leave blank for now 15 | - Main Body - Whether this is a main body section or an introduction / conclusion section 16 | 17 | Final check: 18 | 1. Confirm that the Sections follow the structure EXACTLY as shown above 19 | 2. Confirm that each Section Description has a clearly stated scope that does not conflict with other sections 20 | 3. Confirm that the Sections are grounded in the user notes""" 21 | 22 | # Section writer instructions 23 | main_body_section_writer_instructions = """You are an expert technical writer crafting one section of a blog post. 24 | 25 | Here are the user instructions for the overall blog post, so that you have context for the overall story: 26 | {user_instructions} 27 | 28 | Here is the Section Name you are going to write: 29 | {section_name} 30 | 31 | Here is the Section Description you are going to write: 32 | {section_topic} 33 | 34 | Use this information from various scraped source urls to flesh out the details of the section: 35 | {source_urls} 36 | 37 | WRITING GUIDELINES: 38 | 39 | 1. Style Requirements: 40 | - Use technical and precise language 41 | - Use active voice 42 | - Zero marketing language 43 | 44 | 2. Format: 45 | - Use markdown formatting: 46 | * ## for section heading 47 | * ``` for code blocks 48 | * ** for emphasis when needed 49 | * - for bullet points if necessary 50 | - Do not include any introductory phrases like 'Here is a draft...' or 'Here is a section...' 51 | 52 | 3. Grounding: 53 | - ONLY use information from the source urls provided 54 | - Do not include information that is not in the source urls 55 | - If the source urls are missing information, then provide a clear "MISSING INFORMATION" message to the writer 56 | 57 | QUALITY CHECKLIST: 58 | [ ] Meets word count as specified in the Section Description 59 | [ ] Contains one clear code example if specified in the Section Description 60 | [ ] Uses proper markdown formatting 61 | 62 | Generate the section content now, focusing on clarity and technical accuracy.""" 63 | 64 | # Intro/conclusion instructions 65 | intro_conclusion_instructions = """You are an expert technical writer crafting the introduction or conclusion of a blog post. 66 | 67 | Here is the Section Name you are going to write: 68 | {section_name} 69 | 70 | Here is the Section Description you are going to write: 71 | {section_topic} 72 | 73 | Here are the main body sections that you are going to reference: 74 | {main_body_sections} 75 | 76 | Here are the URLs that you are going to reference: 77 | {source_urls} 78 | 79 | Guidelines for writing: 80 | 81 | 1. Style Requirements: 82 | - Use technical and precise language 83 | - Use active voice 84 | - Zero marketing language 85 | 86 | 2. Section-Specific Requirements: 87 | 88 | FOR INTRODUCTION: 89 | - Use markdown formatting: 90 | * # Title (must be attention-grabbing but technical) 91 | 92 | FOR CONCLUSION: 93 | - Use markdown formatting: 94 | * ## Conclusion (crisp concluding statement)""" -------------------------------------------------------------------------------- /src/agent/state.py: -------------------------------------------------------------------------------- 1 | import operator 2 | from dataclasses import dataclass, field 3 | from pydantic import BaseModel, Field 4 | from typing_extensions import Annotated, List 5 | 6 | class Section(BaseModel): 7 | name: str = Field( 8 | description="Name for this section of the report.", 9 | ) 10 | description: str = Field( 11 | description="Brief overview of the main topics and concepts to be covered in this section.", 12 | ) 13 | content: str = Field( 14 | description="The content of the section." 15 | ) 16 | main_body: bool = Field( 17 | description="Whether this is a main body section." 18 | ) 19 | 20 | class Sections(BaseModel): 21 | sections: List[Section] = Field( 22 | description="Sections of the report.", 23 | ) 24 | 25 | @dataclass(kw_only=True) 26 | class BlogState: 27 | transcribed_notes_file: str 28 | urls: List[str] = field(default_factory=list) # List of urls 29 | sections: list[Section] = field(default_factory=list) 30 | completed_sections: Annotated[list, operator.add] # Send() API key 31 | blog_main_body_sections: str = field(default=None) # Main body sections from research 32 | final_blog: str = field(default=None) # Final report 33 | 34 | @dataclass(kw_only=True) 35 | class BlogStateInput: 36 | transcribed_notes_file: str # Blog notes 37 | urls: List[str] = field(default_factory=list) # List of urls 38 | 39 | @dataclass(kw_only=True) 40 | class BlogStateOutput: 41 | final_blog: str = field(default=None) # Final report 42 | 43 | @dataclass(kw_only=True) 44 | class SectionState: 45 | section: Section # Report section 46 | transcribed_notes_file: str = field(default=None) 47 | urls: List[str] = field(default_factory=list) # List of urls 48 | blog_main_body_sections: str = field(default=None) # Main body sections from research 49 | completed_sections: list[Section] = field(default_factory=list) # Final key we duplicate in outer state for Send() API 50 | 51 | @dataclass(kw_only=True) 52 | class SectionOutputState: 53 | completed_sections: list[Section] = field(default_factory=list) # Final key we duplicate in outer state for Send() API -------------------------------------------------------------------------------- /src/agent/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from langchain_community.document_loaders import WebBaseLoader 3 | from agent.state import Section 4 | 5 | def load_and_format_urls(url_list): 6 | """Load web pages from URLs and format them into a readable string. 7 | 8 | Args: 9 | url_list (str or list): Single URL or list of URLs to load and format 10 | 11 | Returns: 12 | str: Formatted string containing metadata and content from all loaded documents, 13 | separated by '---' delimiters. Each document includes: 14 | - Title 15 | - Source URL 16 | - Description 17 | - Page content 18 | """ 19 | 20 | # Clean and normalize the input URLs 21 | if not isinstance(url_list, list): 22 | raise ValueError("url_list must be a list of strings") 23 | 24 | # Clean each URL in the list 25 | urls = [url.strip() for url in url_list if url.strip()] 26 | 27 | loader = WebBaseLoader(urls) 28 | docs = loader.load() 29 | 30 | formatted_docs = [] 31 | 32 | for doc in docs: 33 | # Format metadata 34 | metadata_str = ( 35 | f"Title: {doc.metadata.get('title', 'N/A')}\n" 36 | f"Source: {doc.metadata.get('source', 'N/A')}\n" 37 | f"Description: {doc.metadata.get('description', 'N/A')}\n" 38 | ) 39 | 40 | # Format content (strip extra whitespace and newlines) 41 | content = doc.page_content.strip() 42 | 43 | # Combine metadata and content 44 | formatted_doc = f"---\n{metadata_str}\nContent:\n{content}\n---" 45 | formatted_docs.append(formatted_doc) 46 | 47 | # Join all documents with double newlines 48 | return "\n\n".join(formatted_docs) 49 | 50 | def read_dictation_file(file_path: str) -> str: 51 | """Read content from a text file audio-to-text dictation.""" 52 | try: 53 | # Try to get directory from __file__ (for module imports) 54 | current_dir = os.path.dirname(os.path.abspath(__file__)) 55 | except NameError: 56 | # Fallback for Jupyter notebooks 57 | current_dir = os.getcwd() 58 | notes_dir = os.path.join(current_dir, "notes") 59 | absolute_path = os.path.join(notes_dir, file_path) 60 | print(f"Reading file from {absolute_path}") 61 | try: 62 | with open(absolute_path, 'r', encoding='utf-8') as file: 63 | return file.read() 64 | except FileNotFoundError: 65 | print(f"Warning: File not found at {absolute_path}") 66 | return "" 67 | except Exception as e: 68 | print(f"Error reading file: {e}") 69 | return "" 70 | 71 | def format_sections(sections: list[Section]) -> str: 72 | """ Format a list of sections into a string """ 73 | formatted_str = "" 74 | for idx, section in enumerate(sections, 1): 75 | formatted_str += f""" 76 | {'='*60} 77 | Section {idx}: {section.name} 78 | {'='*60} 79 | Description: 80 | {section.description} 81 | Main body: 82 | {section.main_body} 83 | Content: 84 | {section.content if section.content else '[Not yet written]'} 85 | """ 86 | return formatted_str --------------------------------------------------------------------------------