{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Journalism-Focused AI Assistant\n", "\n", "![Journalism Focused AI Assistant](../images/journalism_focused_ai_assistant_langgraph.png)\n", "\n", "## Overview\n", "\n", "This notebook introduces an AI-powered assistant designed specifically for journalists, tackling challenges like misinformation, biased reporting, and information overload. With tools for fact-checking, tone analysis, summarization, and more, it uses AI tools to enhance the accuracy and efficiency of journalistic work.\n", "\n", "\n", "## Motivation\n", "\n", "Journalism plays a vital role in upholding democracy, but modern challenges like the flood of online misinformation and subtle biases can undermine trust in reporting. Journalists often face the daunting task of making sense of huge volumes of data under tight deadlines. This notebook equips journalists with tools to:\n", "\n", "- **Verify claims** through reliable fact-checking.\n", "- **Detect tone and biases** to maintain balanced storytelling.\n", "- **Simplify the review process** with concise and accurate summaries, as well as grammar checks.\n", "\n", "The ultimate goal is to support ethical reporting and uphold the integrity of the information we rely on every day.\n", "\n", "## Key Components\n", "\n", "1. **Language Models**: Get insights and generate responses using advanced models like `Llama 3.1/3.2` and `gpt-4o-mini`.\n", "2. **Web Search Integration**: Fetch reliable data from `DuckDuckGo’s` search API to strengthen the research process.\n", "3. **Document Parsing**: Extract text from PDFs and web pages with tools like PyMuPDFLoader and WebBaseLoader, enhanced by BeautifulSoupTransformer.\n", "4. **Structured Outputs**: Receive responses in a clean, JSON format for consistency and precision.\n", "5. **Text Splitting and Summarization**: Break down long articles into digestible summaries using RecursiveCharacterTextSplitter.\n", "6. **Tailored Prompts and Examples**: Use custom prompts and few-shot prompting to guide the AI in providing meaningful results.\n", "7. **LangGraph Workflow**: Tie everything together into a seamless, easy-to-use workflow.\n", "\n", "## Method Details\n", "\n", "### Setting Up the Environment\n", "- Import necessary libraries.\n", "- Configure any API keys and data sources.\n", "\n", "### Summarization\n", "- Pinpoint key ideas in long articles or reports.\n", "- Generate clear, concise summaries for quicker understanding.\n", "\n", "### Fact-Checking\n", "- Input claims or statements to analyze.\n", "- Search credible sources and compile relevant evidence.\n", "- Categorize claims (e.g., confirmed, refuted, or unverifiable) and provide detailed explanations.\n", "\n", "### Tone and Bias Analysis\n", "- Process text to determine sentiment—positive, neutral, or negative.\n", "- Spot and highlight biased language or phrasing.\n", "\n", "### Quote Extraction\n", "- Detect direct quotes and their sources to add transparency to your reporting.\n", "\n", "### Grammar and Bias Review\n", "- Identify grammar errors and subtle biases, ensuring content is polished and fair.\n", "\n", "### LangGraph Workflow Integration\n", "- Use LangGraph to connect all these tools into one powerful workflow:\n", "- Define nodes for each task, from analysis to report generation.\n", "- Pass data seamlessly between tasks for smooth processing.\n", "- Test the workflow on a sample article.\n", "\n", "### Report Generation\n", "- Combine all findings into a well-organized report that’s easy to read and share.\n", "\n", "### Additional Considerations\n", "- Discuss limitations, potential improvements, or specific use cases.\n", "\n", "## This Journalism-Focused AI Assistant is all about helping journalists do their best work by:\n", "- **Improving Accuracy**: Fact-checking tools ensure your claims are backed by evidence.\n", "- **Boosting Efficiency**: Summarization and workflows save valuable time.\n", "- **Adding Transparency**: Features like quote extraction and structured reports build trust.\n", "- **Promoting Ethical Reporting**: Tone and bias analysis helps maintain objectivity.\n", "\n", "By bringing these features together, this tool empowers journalists to focus on what they do best: telling meaningful stories." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup and Imports\n", "\n", "First, we'll import the necessary modules and set up our environment." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Required packages\n", "# %pip install beautifulsoup4 duckduckgo-search langchain langgraph langchain-ollama langchain-openai langchain-openai python-dotenv" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import pprint\n", "import time\n", "\n", "from pathlib import Path\n", "from functools import lru_cache\n", "from dotenv import load_dotenv\n", "from typing import Optional, List, TypedDict\n", "from duckduckgo_search import DDGS\n", "from IPython.display import display, Image\n", "\n", "from langchain_openai import ChatOpenAI\n", "from langchain_core.prompts import PromptTemplate\n", "from langchain_community.document_loaders.pdf import PyMuPDFLoader\n", "from langchain_community.document_loaders import WebBaseLoader\n", "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", "from langchain_community.document_transformers import BeautifulSoupTransformer\n", "from langgraph.graph import StateGraph, END\n", "from langchain_ollama import ChatOllama\n", "\n", "# Load environment variables\n", "load_dotenv()\n", "os.environ[\"OPENAI_API_KEY\"] = os.getenv('OPENAI_API_KEY')\n", "\n", "# Define the data path\n", "data_path = Path(os.getcwd()).parent / \"data\"\n", "\n", "# Duckduckgo search\n", "ddgs = DDGS()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initialize Language Models\n", "\n", "We will initialize the language models that can be used for testing.\n", "\n", "For running Llama models, we use Ollama. A detailed tutorial on this is beyond the scope of this notebook, but you can refer to their repository for a [quickstart guide on Ollama](https://github.com/ollama/ollama)." ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [], "source": [ "llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n", "# llm = ChatOllama(model=\"llama3.1\", temperature=0)\n", "# llm = ChatOllama(model=\"llama3.2\", temperature=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data Setup\n", "\n", "For this, I used the `gpt-4o` model to generate a sample article using the prompt below. Note that the information mentioned in the article is not to be taken seriously. The article will serve as input for summarization, fact-checking, tone analysis, quote extraction, and grammar and bias analysis modules, providing a basis for refining prompt responses.\n", "\n", "We will take advantage of the `document_loaders` `langchain` module, specifically the `PyMuPDFLoader` for loading the text from a PDF file. \n", "\n", "`Prompt`:\n", "Write an article designed for classification purposes, containing a variety of claims that fit into distinct but subtly presented categories: well-known and confirmed facts, refuted claims, unverifiable statements requiring further research, and vague or speculative assertions. The article should flow naturally without explicitly labeling these categories but ensure that each type of claim is clearly identifiable through its content and context. Use varied tones, including positive, critical, biased, or opinionated language, to differentiate the claims. Incorporate quotes to enhance realism and include occasional minor grammar errors or awkward phrasing for added authenticity.\n" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [], "source": [ "# Pdf file path\n", "file_path = data_path / \"Sample AI Generated Article.pdf\"\n", "\n", "# Load the pdf file\n", "pages = []\n", "loader = PyMuPDFLoader(file_path)\n", "for page in loader.lazy_load():\n", " pages.append(page)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Check the file contents and remove the new lines and tabs\n", "\n", "For printing throughout the tutorial, I will sometimes use the built-in `pprint` package as it formats text and different data types in more human-readable forms." ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Page content before cleaning\n", "('The Mysterious Origins and Potential\\n'\n", " 'Impacts of the Halcyon Bird\\n'\n", " 'Introduction\\n'\n", " 'The halcyon bird, a su')\n", "('Sailors’ Tales and Anecdotes\\n'\n", " 'Stories passed down through generations speak of sailors encountering h')\n", "('\"People are drawn to the idea of a creature that embodies serenity,\" says '\n", " 'cultural historian\\n'\n", " 'Dr. Ste')\n", "\n", "Page content after cleaning\n", "('The Mysterious Origins and Potential Impacts of the Halcyon Bird '\n", " 'Introduction The halcyon bird, a subject of fascination for centuries, is '\n", " 'often celebrated as a symbol of tranquility and mythical wond')\n", "('Sailors’ Tales and Anecdotes Stories passed down through generations speak '\n", " 'of sailors encountering halcyon birds during times of storm and finding '\n", " 'themselves inexplicably drawn to safety. Captain Ed H')\n", "('\"People are drawn to the idea of a creature that embodies serenity,\" says '\n", " 'cultural historian Dr. Stephen Archer. \"It’s a universal longing, especially '\n", " 'in turbulent times.\" Modern art and media have al')\n" ] } ], "source": [ "def clean_page_content(page_content: str) -> str:\n", " \"\"\"\n", " Clean the page content by removing new lines and tabs\n", " \"\"\"\n", " page_content = page_content.replace(\"\\n\", \" \")\n", " page_content = page_content.replace(\"\\t\", \" \")\n", " return page_content\n", "\n", "print(\"Page content before cleaning\")\n", "for page in pages:\n", " pprint.pprint(page.page_content[:100])\n", " \n", "\n", "print(\"\\nPage content after cleaning\")\n", "formatted_pages = []\n", "for page in pages:\n", " page_content = clean_page_content(page.page_content)\n", " formatted_pages.append(page_content)\n", " pprint.pprint(page_content[:200])\n", "\n", "# Combine all the pages into a single text\n", "full_article_text = \" \".join(formatted_pages)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Helper Functions" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [], "source": [ "def chunk_large_text(text, chunk_size=100000, overlap=1000):\n", " \"\"\"\n", " Splits the input text into manageable chunks while maintaining context overlap.\n", " \"\"\"\n", " text_splitter = RecursiveCharacterTextSplitter(\n", " chunk_size=chunk_size,\n", " chunk_overlap=overlap,\n", " separators=[\"\\n\\n\", \"\\n\", \" \", \"\"]\n", " )\n", " return text_splitter.split_text(text)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Summarizing Long Articles\n", "\n", "Summarizing lengthy articles requires handling the constraints of token limits in models like `gpt-4o-mini` or `Llama 3.2/3.1`. These models can process only a limited amount of text at once, making **text splitting** a crucial step to ensure the analysis is thorough and no critical information is overlooked. While this notebook may not reach those token limits, the text-splitting approach is demonstrated as a proof of concept.\n", "\n", "### Why Use Text Splitting?\n", "\n", "Text splitting ensures the model can process lengthy content effectively by:\n", "- Dividing large articles into manageable chunks (e.g., 100,000 tokens).\n", "- Maintaining context through overlapping sections (e.g., 1,000 tokens).\n", "- Preventing request rejections due to exceeding token limits.\n", "\n", "### Tools and Techniques\n", "\n", "- **Splitter**: The `RecursiveCharacterTextSplitter` from `langchain` is used for breaking down text into smaller chunks while preserving context and readability.\n", "- **Custom Prompts**: Focus prompts on extracting key events, individuals, and statistics for precise summarization.\n", "- **Step-by-Step Workflow**:\n", " 1. Split the text into chunks.\n", " 2. Summarize each chunk individually using the AI model.\n", " 3. Combine the individual summaries into a cohesive and concise final summary.\n", "\n", "### Benefits of This Approach\n", "\n", "By leveraging text splitting and summarization workflows, even the longest articles can be processed effectively:\n", "- **Accuracy**: Ensures no critical details are missed during summarization.\n", "- **Efficiency**: Breaks down complex tasks into manageable pieces for faster results.\n", "- **Consistency**: Maintains flow and context across the final summary.\n", "\n", "This approach provides reliable and concise summaries for articles of any length, making it a powerful tool for handling extensive content with ease.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Prepare the summarization pipelines" ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [], "source": [ "# Define the summarization prompt\n", "summarization_prompt = PromptTemplate(\n", " input_variables=[\"text\"],\n", " template=(\n", " \"Summarize the provided article text focusing on the main events, key people involved, \"\n", " \"and any important statistics in 150-200 words. Use a neutral tone suitable for a journalistic report:\\n\\n\"\n", " \"Article text:\\n{text}\\n\\n\"\n", " )\n", ")\n", "\n", "# Define de combine summarization prompt\n", "combine_summarization_prompt = PromptTemplate(\n", " input_variables=[\"summaries\"],\n", " template=(\n", " \"Combine the provided summaries into a single coherent summary that captures the main events, key people involved, \"\n", " \"and important statistics in 150-200 words. Use a neutral tone suitable for a journalistic report:\\n\\n\"\n", " \"Summaries:\\n{summaries}\\n\\n\"\n", " )\n", ")\n", "\n", "# Define the summarization pipeline\n", "summarization_pipeline = summarization_prompt | llm\n", "\n", "# Define the combine summarization pipeline\n", "combine_summarization_pipeline = combine_summarization_prompt | llm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Summarization helper functions" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [], "source": [ "def combine_summaries(summaries: List[str]):\n", " \"\"\"\n", " Combines multiple summaries into a single coherent summary.\n", " \"\"\"\n", " # If the article is short, return the single summary\n", " if len(summaries) == 1:\n", " return summaries[0]\n", " \n", " # Combine the summaries into a single text\n", " summaries_text = \"\"\n", " for i, summary in enumerate(summaries):\n", " summaries_text += f\"Summary {i + 1}:\\n{summary}\\n\\n\"\n", " \n", " # Generate a combined summary\n", " full_summary = combine_summarization_pipeline.invoke({\"summaries\": summaries_text})\n", "\n", " return full_summary\n", "\n", "\n", "def summarize_article(article_text: str, article_chunks=None):\n", " \"\"\"\n", " Summarize a full article text by splitting it into manageable chunks and generating summaries for each chunk.\n", " The individual summaries are then combined into a single coherent summary.\n", " \"\"\"\n", " # Split the full article text into manageable chunks if not provided\n", " if not article_chunks:\n", " article_chunks = chunk_large_text(article_text)\n", "\n", " # Generate summaries for each chunk\n", " summaries = []\n", " for chunk in article_chunks:\n", " summary = summarization_pipeline.invoke({\"text\": chunk})\n", " summaries.append(summary.content)\n", "\n", " # Combine the individual summaries into a single coherent summary\n", " full_summary = combine_summaries(summaries)\n", " \n", " return full_summary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sample usage" ] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "Summary of the article\n", "('The halcyon bird, often associated with tranquility and mythical narratives, '\n", " 'has captivated human imagination for centuries. Rooted in Greek mythology, '\n", " 'particularly the story of Alcyone, the term \"halcyon\" refers to certain '\n", " 'kingfisher species, primarily found in tropical regions. Dr. Elena Marquez, '\n", " 'an avian biology professor, clarifies that while these birds exhibit calm '\n", " 'behaviors, they are adaptations for survival rather than evidence of '\n", " 'supernatural abilities. Modern science, represented by marine biologist Dr. '\n", " 'Robert Lyle, dismisses claims that halcyon birds can calm the seas, '\n", " 'attributing such phenomena to seasonal weather patterns.\\n'\n", " '\\n'\n", " 'Anecdotal tales, like that of Captain Ed Hartley, recount sailors '\n", " 'encountering halcyon birds during storms, though these stories lack '\n", " 'documentation. Conservation debates arise, with activists like Lorraine '\n", " \"Feldman advocating for habitat protection linked to the bird's cultural \"\n", " 'significance, while critics like Richard Knowles argue for focusing on more '\n", " 'pressing environmental issues. The halcyon bird also influences modern '\n", " 'culture, symbolizing peace and resilience in art and media. Researchers are '\n", " 'exploring its anatomical features for potential innovations in technology, '\n", " 'highlighting the intersection of myth, culture, and science in understanding '\n", " 'this enigmatic bird.')\n" ] } ], "source": [ "summary_result = summarize_article(full_article_text)\n", "\n", "print(\"\\n\\nSummary of the article\")\n", "pprint.pprint(summary_result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fact-Checking Articles\n", "\n", "This section outlines a structured approach to verify claims in articles, ensuring accuracy and credibility.\n", "\n", "### Key Components\n", "\n", "- **Structured Outputs**: Results are formatted as JSON with:\n", " - **Statement**: The claim being analyzed.\n", " - **Status**: `confirmed`, `refuted`, `unverifiable`, or `vague`.\n", " - **Explanation**: A rationale for the status.\n", " - **Keywords**: Suggested for further investigation.\n", " - Implemented using the `with_structured_output` method for consistency.\n", "\n", "- **Search Integration**:\n", " - DuckDuckGo’s API retrieves relevant search results.\n", " - Tools like `WebBaseLoader` and `BeautifulSoupTransformer` extract and clean web content\n", "\n", "- **Tailored Prompting**: A custom prompt guides the AI to analyze claims, flag inaccuracies, and suggest further research paths.\n", "\n", "### Workflow\n", "\n", "1. Extract claims from the text.\n", "2. Fetch evidence using web search.\n", "3. Analyze and categorize claims with supporting explanations.\n", "4. Output findings in a structured, clear format.\n", "\n", "### Benefits\n", "\n", "This approach combines AI precision with real-time data to deliver:\n", "- **Transparency**: Claims are backed by evidence.\n", "- **Efficiency**: Automated workflows save time.\n", "- **Consistency**: Standardized outputs ensure reliability.\n", "\n", "A seamless tool to enhance content credibility and journalistic integrity.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The models used for the structured output" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [], "source": [ "class FactCheckStatement(TypedDict):\n", " \"\"\"\n", " Represents a single fact-check statement structure.\n", " \"\"\"\n", " statement: str\n", " status: str\n", " explanation: str\n", " suggested_keywords: List[str]\n", "\n", "\n", "class FactCheckResult(TypedDict):\n", " \"\"\"\n", " Represents the result of a fact-checking process.\n", " \"\"\"\n", " result: List[FactCheckStatement]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Define the internet search helper functions" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [], "source": [ "# The search_ddg function is cached to avoid repeated searches for the same keywords\n", "# This was useful to test the search and summarization pipeline without waiting for the search results each time\n", "@lru_cache\n", "def search_ddg(keywords: str, max_results: int = 1):\n", " \"\"\"\n", " This function searches DuckDuckGo for the given keywords and returns the top max_results results.\n", " \"\"\"\n", " # in case of timeout wait retry after 5 seconds\n", " try:\n", " text_results = ddgs.text(keywords=keywords, max_results=max_results)\n", " except Exception as e:\n", " print(\"Keywords\", keywords)\n", " print(\"Error: \", str(e))\n", " time.sleep(5)\n", " try:\n", " text_results = ddgs.text(keywords=keywords, max_results=max_results)\n", " except Exception as e:\n", " print(\"Error: \", str(e))\n", " return [{}]\n", " \n", " return text_results\n", "\n", "\n", "\n", "def search_and_summarize(keywords: str, max_results: int = 1):\n", " \"\"\"\n", " Search for the given keywords using DuckDuckGo and summarize the content of the top search results.\n", " \"\"\"\n", " text_results = search_ddg(keywords, max_results)\n", "\n", " results = []\n", " for result in text_results:\n", " loader = WebBaseLoader([str(result['href'])])\n", " html_content = str(loader.scrape())\n", " bs_transformer = BeautifulSoupTransformer()\n", " html_transform = (\n", " bs_transformer.remove_unwanted_tags(html_content, [\"script\", \"style\", \"noscript\"])\n", " )\n", " \n", " # Based on various attempt I oberserved that the content is mostly in

tags,\n", " # so I am extracting only

tags, but is not the best approach for all the websites\n", " html_transform = bs_transformer.extract_tags(html_transform, [\"p\"], remove_comments=True)\n", " html_transform = bs_transformer.remove_unnecessary_lines(html_transform)\n", "\n", " # summarize the content using the previously defined summarization pipeline\n", " summary_result = summarize_article(page_content)\n", "\n", " results.append({\n", " \"title\": result['title'],\n", " \"url\": result['href'],\n", " \"summary\": summary_result\n", " })\n", "\n", " return results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Prepare the fact-checking pipeline" ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [], "source": [ "# Define the fact-checking prompt\n", "fact_checking_prompt = PromptTemplate(\n", " input_variables=[\"text\"],\n", " template=(\n", " \"Fact-check the texts provided. For each statement, identify any factual inaccuracies, misleading information, \"\n", " \"unsupported claims, or vague language lacking specific details. Confirm accuracy for each claim where possible, \"\n", " \"or provide suggestions for further searches. Flag statements as 'vague' if they are overly broad or lacking \"\n", " \"critical specifics (e.g., missing names, dates, or descriptions of technologies).\"\n", " \"Suggest keyword if you can't confirm or refute the statement.\\n\\n\"\n", " \"{text}\\n\\n\"\n", " \"Return the results in this JSON format:\\n\"\n", " \"{{\\n\"\n", " \" \\\"results\\\": [\\n\"\n", " \" {{\\n\"\n", " \" \\\"statement\\\": \\\"\\\",\\n\"\n", " \" \\\"status\\\": \\\"\\\",\\n\"\n", " \" \\\"explanation\\\": \\\"\\\",\\n\"\n", " \" \\\"suggested_keywords\\\": [\\\"\\\", \\\"\\\"]\\n\"\n", " \" }},\\n\"\n", " \" {{...}}\\n\"\n", " \" ]\\n\"\n", " \"}}\\n\"\n", " )\n", ")\n", "\n", "# Define the structured output llm for the fact-checking pipeline\n", "structured_output_llm = llm.with_structured_output(FactCheckResult)\n", "\n", "# Define the fact-checking pipeline\n", "fact_checking_pipeline = fact_checking_prompt | structured_output_llm\n", "\n", "\n", "\n", "def fact_check_article(article_text: str, chunks=None):\n", " \"\"\"\n", " Fact-check the given text by identifying factual inaccuracies, misleading information, unsupported claims, or vague language.\n", " \"\"\"\n", " # Split the full article text into manageable chunks if not provided\n", " if not chunks:\n", " chunks = chunk_large_text(article_text)\n", " \n", " # Fact-check each chunk of the article\n", " fact_check_results = []\n", " for chunk in chunks:\n", " fact_check_result = fact_checking_pipeline.invoke({\"text\": chunk})\n", " # Add search results for suggested keywords\n", " for statement in fact_check_result[\"result\"]:\n", " suggested_keywords = statement.get('suggested_keywords', [])\n", " if suggested_keywords:\n", " statement['search_results'] = [\n", " search_and_summarize(keyword) for keyword in suggested_keywords\n", " ]\n", " \n", " fact_check_results.extend(fact_check_result[\"result\"])\n", "\n", " return fact_check_results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sample usage" ] }, { "cell_type": "code", "execution_count": 78, "metadata": {}, "outputs": [], "source": [ "# Fact-check the full article text\n", "fact_check_results = fact_check_article(full_article_text)" ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", " Unverifiable facts\n" ] } ], "source": [ "# Explore the fact-checking results\n", "# print(\"\\n\\n Confirmed facts\")\n", "# for statement in fact_check_results:\n", "# if statement[\"status\"] == \"confirmed\":\n", "# pprint.pprint(statement)\n", "\n", "# print(\"\\n\\n Refuted facts\")\n", "# for statement in fact_check_results:\n", "# if statement[\"status\"] == \"refuted\":\n", "# pprint.pprint(statement)\n", "\n", "print(\"\\n\\n Unverifiable facts\")\n", "for statement in fact_check_results:\n", " if statement[\"status\"] == \"unverifiable\":\n", " pprint.pprint(statement)\n", " \n", "# print(\"\\n\\n Vague facts\")\n", "# for statement in fact_check_results:\n", "# if statement[\"status\"] == \"vague\":\n", "# pprint.pprint(statement)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For our case, it seems that multiple sources return similar information. This redundancy can be useful for verifying the consistency and reliability of the data, but it also highlights the importance of cross-referencing to ensure accuracy." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tone and Sentiment Analysis\n", "\n", "This module evaluates the tone of an article to determine if it’s neutral, positive, critical, or opinionated. By identifying tone, journalists can uncover biases and better understand the mood conveyed in the content, ensuring more balanced reporting." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Prepare the tone and sentiment analysis pipeline" ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [], "source": [ "tone_analysis_prompt = PromptTemplate(\n", " input_variables=[\"text\"],\n", " template=(\n", " \"Analyze the tones of the following article. Does it appear neutral, positive, critical, or opinionated? \"\n", " \"Provide a short explanation for each detected tone. \"\n", " \"Use specific examples from the article to support your analysis.\\\\n\\n{text}\"\n", " )\n", ")\n", "\n", "tone_pipeline = tone_analysis_prompt | llm\n", "\n", "\n", "\n", "def tone_analysis_article(article_text: str, chunks=None):\n", " \"\"\"\n", " Analyze the tones of the given article text.\n", " \"\"\"\n", " # Split the full article text into manageable chunks if not provided\n", " if not chunks:\n", " chunks = chunk_large_text(article_text)\n", " \n", " # Analyze the tones of each chunk of the article\n", " tone_results = []\n", " for chunk in chunks:\n", " tone_result = tone_pipeline.invoke({\"text\": chunk})\n", " tone_results.append(tone_result.content)\n", " \n", " return tone_results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sample usage" ] }, { "cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [], "source": [ "tone_analysis_results = tone_analysis_article(full_article_text)" ] }, { "cell_type": "code", "execution_count": 82, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", " Tone analysis of the article\n", "['The article on the halcyon bird exhibits a **neutral tone** overall, with '\n", " 'elements of **admiration** and **skepticism** interspersed throughout. '\n", " 'Here’s a breakdown of the detected tones:\\n'\n", " '\\n'\n", " '1. **Neutral Tone**: The article primarily presents information about the '\n", " 'halcyon bird, its mythological roots, and its ecological significance '\n", " 'without overtly favoring one perspective over another. For instance, it '\n", " 'discusses both the mythical attributes associated with the bird and the '\n", " 'scientific dismissals of those claims. Phrases like \"the belief that halcyon '\n", " 'birds possess the ability to calm the sea is widely dismissed by modern '\n", " 'science\" indicate a balanced presentation of facts.\\n'\n", " '\\n'\n", " '2. **Admiration**: There is a sense of admiration for the halcyon bird, '\n", " 'particularly in how it has inspired cultural narratives and artistic '\n", " 'expressions. The article states, \"the imagery of a bird calming the storm '\n", " 'continues to resonate in art and literature,\" highlighting the bird\\'s '\n", " 'enduring symbolic power. Additionally, the mention of its vibrant plumage '\n", " 'and unique hunting techniques reflects a positive appreciation for its '\n", " 'natural beauty.\\n'\n", " '\\n'\n", " '3. **Skepticism**: The article also conveys skepticism, especially regarding '\n", " 'the mythical claims associated with the halcyon bird. For example, Dr. '\n", " 'Robert Lyle\\'s assertion that \"there’s no evidence supporting such a claim\" '\n", " 'and the acknowledgment that \"no documentation or physical evidence supports '\n", " 'this anecdote\" illustrate a critical stance towards the folklore surrounding '\n", " 'the bird. This skepticism is balanced with an understanding of the cultural '\n", " 'significance of these myths.\\n'\n", " '\\n'\n", " '4. **Opinionated Elements**: While the article maintains a neutral tone, it '\n", " 'does include opinionated statements from various individuals, such as '\n", " 'Lorraine Feldman’s argument for the importance of preserving habitats linked '\n", " 'to the halcyon bird. This introduces a subjective viewpoint into the '\n", " 'discussion, reflecting the ongoing debate about conservation priorities. '\n", " 'Richard Knowles’ dismissal of conservation efforts as “fairy tales” also '\n", " 'adds a critical perspective to the discourse.\\n'\n", " '\\n'\n", " 'In summary, the article effectively balances neutral reporting with elements '\n", " 'of admiration and skepticism, while also incorporating opinionated '\n", " \"viewpoints that enrich the discussion about the halcyon bird's cultural and \"\n", " 'ecological significance.']\n" ] } ], "source": [ "print(\"\\n\\n Tone analysis of the article\")\n", "pprint.pprint(tone_analysis_results)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Quote Extraction\n", "\n", "This module identifies and extracts key quotes from an article, offering insights into important viewpoints and statements. Extracted quotes help journalists highlight perspectives and enrich their reporting with direct references." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Prepare the quote extraction pipeline" ] }, { "cell_type": "code", "execution_count": 83, "metadata": {}, "outputs": [], "source": [ "# Quote extraction\n", "quote_extraction_prompt = PromptTemplate(\n", " input_variables=[\"text\"],\n", " template=(\n", " \"Identify direct quotes in the following content, noting the speaker's name \"\n", " \"and the context of each quote. If there are no quotes, return 'No quotes found'.\\n\\n\"\n", " \"Text: {text}\"\n", " )\n", ")\n", "\n", "# Define the quote extraction pipeline\n", "quote_extraction_pipeline = quote_extraction_prompt | llm\n", "\n", "\n", "\n", "def quote_extraction_article(article_text: str, chunks=None):\n", " \"\"\"\n", " Extract direct quotes from the given article text.\n", " \"\"\"\n", " # Split the full article text into manageable chunks if not provided\n", " if not chunks:\n", " chunks = chunk_large_text(article_text)\n", " \n", " # Extract quotes from each chunk of the article\n", " quote_results = []\n", " for chunk in chunks:\n", " quote_result = quote_extraction_pipeline.invoke({\"text\": chunk})\n", " quote_results.append(quote_result.content)\n", " \n", " return quote_results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sample usage" ] }, { "cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [], "source": [ "quote_extraction_results = quote_extraction_article(full_article_text)" ] }, { "cell_type": "code", "execution_count": 85, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['1. **Dr. Elena Marquez**: \"The halcyon kingfisher is a real species. It\\'s '\n", " 'important not to conflate the myth with the bird’s actual ecological '\n", " 'characteristics.\" \\n'\n", " ' *Context: Dr. Marquez is discussing the distinction between the myth of '\n", " 'the halcyon bird and the actual species of kingfishers.*\\n'\n", " '\\n'\n", " '2. **Dr. Marquez**: \"There’s nothing inherently supernatural about these '\n", " 'behaviors. They’re adaptations for survival, not evidence of mythical '\n", " 'powers.\" \\n'\n", " ' *Context: Dr. Marquez emphasizes that the behaviors of kingfishers are '\n", " 'natural adaptations rather than supernatural phenomena.*\\n'\n", " '\\n'\n", " '3. **Dr. Robert Lyle**: \"There’s no evidence supporting such a claim. While '\n", " 'it’s poetic, attributing meteorological changes to a bird is entirely '\n", " 'without merit.\" \\n'\n", " ' *Context: Dr. Lyle is addressing the belief that halcyon birds can calm '\n", " 'the sea, stating that modern science dismisses this idea.*\\n'\n", " '\\n'\n", " '4. **Captain Ed Hartley**: \"a radiant bird guiding their ship to calm waters '\n", " 'in 1892.\" \\n'\n", " ' *Context: Captain Hartley shares a family anecdote about a halcyon bird '\n", " \"during a storm, illustrating the myth's impact on sailors' tales.*\\n\"\n", " '\\n'\n", " '5. **Captain Ed Hartley**: \"It’s the kind of thing you want to believe, but '\n", " 'I’ll be the first to say it sounds far-fetched.\" \\n'\n", " ' *Context: Hartley reflects on the nature of the anecdote he shared, '\n", " 'acknowledging its implausibility.*\\n'\n", " '\\n'\n", " '6. **Dr. Marquez**: \"If there’s anything to it, we’d need years of study to '\n", " 'draw any conclusions. The evidence, if it exists, is scattered and difficult '\n", " 'to analyze.\" \\n'\n", " \" *Context: Dr. Marquez discusses the speculation about halcyon birds' \"\n", " 'potential magnetic sensitivity and the need for further research.*\\n'\n", " '\\n'\n", " '7. **Lorraine Feldman**: \"This bird’s mythos has captured the public’s '\n", " 'imagination. Preserving these regions protects biodiversity and keeps our '\n", " 'cultural stories alive.\" \\n'\n", " ' *Context: Feldman argues for the importance of conservation efforts '\n", " 'linked to the halcyon bird and its cultural significance.*\\n'\n", " '\\n'\n", " '8. **Richard Knowles**: \"Let’s focus on real, proven issues, not fairy '\n", " 'tales.\" \\n'\n", " ' *Context: Knowles critiques the conservation efforts related to the '\n", " 'halcyon bird, suggesting a more pragmatic approach.*\\n'\n", " '\\n'\n", " '9. **Lorraine Feldman**: \"Even if the myths aren’t literally true, they '\n", " 'encourage people to view the environment with wonder and reverence. That '\n", " 'alone is worth preserving.\" \\n'\n", " ' *Context: Feldman discusses the value of myths in fostering respect for '\n", " 'the environment.*\\n'\n", " '\\n'\n", " '10. **Dr. Stephen Archer**: \"People are drawn to the idea of a creature that '\n", " 'embodies serenity. It’s a universal longing, especially in turbulent '\n", " 'times.\" \\n'\n", " ' *Context: Dr. Archer reflects on the cultural significance of the '\n", " 'halcyon bird as a symbol of peace and hope.*\\n'\n", " '\\n'\n", " '11. **Dr. Marquez**: \"The kingfisher’s beak has already influenced the '\n", " 'design of high-speed trains. It’s a small reminder that nature’s adaptations '\n", " 'often hold answers to human challenges.\" \\n'\n", " ' *Context: Dr. Marquez highlights the practical applications of studying '\n", " \"the halcyon bird's anatomy in engineering and design.*\"]\n" ] } ], "source": [ "pprint.pprint(quote_extraction_results)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Grammar and Bias Analysis\n", "\n", "This module reviews articles for grammatical accuracy and detects potential biases, helping journalists maintain credibility and ensure neutrality in their reporting." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Prepare the grammar and bias analysis pipeline" ] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [], "source": [ "# Define a prompt template for reviewing the grammar and bias of the article\n", "review_prompt = PromptTemplate(\n", " input_variables=[\"text\"],\n", " template=(\n", " \"Review the following article for grammar, spelling, punctuation, and bias. \"\n", " \"Provide feedback on each aspect in form of a list of the issues found and some suggestions for improvement.\\n\\n\"\n", " \"{text}\"\n", " )\n", ")\n", "\n", "# Define the review pipeline\n", "grammar_and_bias_review = review_prompt | llm\n", "\n", "\n", "\n", "def grammary_and_bias_analysis_article(article_text: str, chunks=None):\n", " \"\"\"\n", " Review the given article text for grammar, spelling, punctuation, and bias.\n", " \"\"\"\n", " # Split the full article text into manageable chunks if not provided\n", " if not chunks:\n", " chunks = chunk_large_text(article_text)\n", " \n", " # Review each chunk of the article\n", " review_results = []\n", " for chunk in chunks:\n", " review_result = grammar_and_bias_review.invoke({\"text\": chunk})\n", " review_results.append(review_result.content)\n", " \n", " return review_results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sample usage" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [], "source": [ "grammary_and_bias_analysis_results = grammary_and_bias_analysis_article(full_article_text)" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['Here’s a review of the article \"The Mysterious Origins and Potential Impacts '\n", " 'of the Halcyon Bird,\" focusing on grammar, spelling, punctuation, and bias:\\n'\n", " '\\n'\n", " '### Grammar Issues\\n'\n", " '1. **Sentence Structure**: Some sentences are overly complex and could be '\n", " 'simplified for clarity. For example, \"This idea, while intriguing, remains '\n", " 'unsupported by empirical research\" could be rephrased to \"This intriguing '\n", " 'idea lacks empirical support.\"\\n'\n", " '2. **Subject-Verb Agreement**: In the sentence \"The belief that halcyon '\n", " 'birds possess the ability to calm the sea is widely dismissed by modern '\n", " 'science,\" the subject \"belief\" is singular, which is correct, but the phrase '\n", " 'could be clearer if restructured to emphasize the dismissal by science.\\n'\n", " '\\n'\n", " '### Spelling Issues\\n'\n", " '- No spelling errors were found in the article.\\n'\n", " '\\n'\n", " '### Punctuation Issues\\n'\n", " '1. **Comma Usage**: In the sentence \"Interestingly, kingfishers have long '\n", " 'captured the attention of naturalists due to their vibrant plumage and '\n", " 'unique hunting techniques,\" the comma after \"Interestingly\" is correct, but '\n", " 'the sentence could benefit from a more straightforward structure.\\n'\n", " '2. **Quotation Marks**: Ensure consistent use of quotation marks. For '\n", " 'example, in the quote from Dr. Marquez, the punctuation should be inside the '\n", " 'quotation marks: \"There’s nothing inherently supernatural about these '\n", " 'behaviors,\" should be followed by a period inside the quotes if it ends the '\n", " 'sentence.\\n'\n", " '\\n'\n", " '### Bias Issues\\n'\n", " '1. **Balanced Perspectives**: The article presents a balanced view of the '\n", " 'halcyon bird, but it could benefit from more diverse perspectives. For '\n", " 'instance, while it mentions skepticism from scientists, it could also '\n", " 'include viewpoints from cultural historians or indigenous communities that '\n", " 'may hold different beliefs about the bird.\\n'\n", " '2. **Language Choices**: Phrases like \"dismissed by modern science\" could '\n", " 'imply a bias against traditional beliefs. A more neutral phrasing, such as '\n", " '\"not supported by scientific evidence,\" would be less dismissive.\\n'\n", " '3. **Cultural Sensitivity**: When discussing myths and folklore, it’s '\n", " 'important to approach the subject with respect. The article could '\n", " 'acknowledge the cultural significance of these myths to various communities '\n", " 'rather than framing them solely as \"fairy tales.\"\\n'\n", " '\\n'\n", " '### Suggestions for Improvement\\n'\n", " '1. **Simplify Complex Sentences**: Break down longer sentences into shorter, '\n", " 'clearer ones to enhance readability.\\n'\n", " '2. **Enhance Objectivity**: Use more neutral language when discussing '\n", " 'differing viewpoints to avoid implying bias.\\n'\n", " '3. **Include Diverse Perspectives**: Incorporate quotes or insights from a '\n", " 'wider range of experts, including cultural historians or representatives '\n", " 'from communities that hold the halcyon bird in high regard.\\n'\n", " '4. **Clarify Scientific Claims**: When discussing scientific dismissals of '\n", " 'myths, provide context or examples of how these myths have been studied or '\n", " 'debunked to give readers a clearer understanding of the scientific process.\\n'\n", " '5. **Consistent Quotation Formatting**: Ensure that all quotations are '\n", " 'punctuated consistently and correctly, with attention to the placement of '\n", " 'punctuation relative to quotation marks.\\n'\n", " '\\n'\n", " 'By addressing these issues, the article can improve its clarity, '\n", " 'objectivity, and overall impact on readers.']\n" ] } ], "source": [ "pprint.pprint(grammary_and_bias_analysis_results)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## LangGraph Workflow Integration\n", "\n", "This section combines all modules into a unified workflow using LangGraph. By integrating summarization, fact-checking, tone analysis, quote extraction, and grammar and bias review, it creates a robust AI assistant tailored for journalism." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Define State Structure\n", "\n", "The state structure is defined using the `State` class, which is a `TypedDict`. This structure holds various elements of the article analysis process, including the current query, article text, chunks of the article, actions to be performed, and results from different analysis modules. The state structure ensures that all necessary information is organized and accessible throughout the workflow.\n", "\n", "The `State` class includes the following fields:\n", "- `current_query`: The user's current query or request.\n", "- `article_text`: The full text of the article to be analyzed.\n", "- `chunks`: List of text chunks for processing.\n", "- `actions`: List of actions to be performed based on the user's query.\n", "- `summary_result`: Result of the summarization process.\n", "- `fact_check_result`: Result of the fact-checking process.\n", "- `tone_analysis_result`: Result of the tone analysis process.\n", "- `quote_extraction_result`: Result of the quote extraction process.\n", "- `grammar_and_bias_review_result`: Result of the grammar and bias review process.\n", "- `review_result`: Overall review result combining all analyses.\n" ] }, { "cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [], "source": [ "class State(TypedDict):\n", " current_query: str\n", " article_text: str\n", " chunks: List[str]\n", " actions: List[str]\n", " summary_result: Optional[str]\n", " fact_check_result: Optional[TypedDict]\n", " tone_analysis_result: Optional[str]\n", " quote_extraction_result: Optional[str]\n", " grammar_and_bias_review_result: Optional[str]\n", " review_result: Optional[str]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Pipeline for Identifying Action Types\n", "\n", "This step helps the assistant figure out what you’re asking for—whether it’s summarizing content, fact-checking, analyzing tone, extracting quotes, reviewing grammar and bias, or determining that no action is needed.\n", "\n", "Using a well-crafted prompt template, the assistant organizes its response in a clear JSON format, making it easy to understand and act on. To make sure the responses are accurate, I’ve included `few-shot prompting`. By showing the model examples of what good responses look like, it learns to interpret requests more effectively." ] }, { "cell_type": "code", "execution_count": 90, "metadata": {}, "outputs": [], "source": [ "class SystemAction(TypedDict):\n", " actions: List[str]\n", "\n", "# Define a prompt template for identifying the user's intended actions based on their input\n", "action_prompt = PromptTemplate(\n", " input_variables=[\"input_text\"],\n", " template=(\n", " \"Identify the user's intended actions based on their input and return the actions in the following JSON format:\\n\"\n", " \"{{\\n\"\n", " ' \"actions\": [\"\"]\\n'\n", " \"}}\\n\\n\"\n", " \"Guidelines:\\n\"\n", " \"- If the user requests all actions or says 'everything' or 'full report,' respond with the list of all individual actions:\\n\"\n", " '{{\\n'\n", " ' \"actions\": [\"summarization\", \"fact-checking\", \"tone-analysis\", \"quote-extraction\", \"grammar-and-bias-review\"]\\n'\n", " \"}}\\n\"\n", " \"- If the user input requests multiple specific actions, list each action requested (e.g., 'summarization' and 'tone analysis' together as ['summarization', 'tone-analysis']).\\n\"\n", " \"- If the user’s input does not relate to any accessible action, respond with:\\n\"\n", " '{{\\n'\n", " ' \"actions\": [\"invalid\"]\\n'\n", " \"}}\\n\"\n", " \"- If the user's input does not require any specific action, or wants to end the conversation, respond with:\\n\"\n", " '{{\\n'\n", " ' \"actions\": [\"no-action-required\"]\\n'\n", " \"}}\\n\\n\"\n", " \"Important:\\n\"\n", " \"- Only list all actions ('summarization', 'fact-checking', 'tone-analysis', 'quote-extraction', 'grammar-and-bias-review') if the user explicitly requests a comprehensive overview or all actions.\\n\"\n", " \"- List only the actions explicitly requested by the user without inferring additional ones.\\n\\n\"\n", " \"Examples:\\n\"\n", " \"- User input: 'Can you summarize the main points of this article for me?'\\n\"\n", " ' System action: {{ \"actions\": [\"summarization\"] }}\\n'\n", " \"- User input: 'I need to verify some claims in this article. Can you fact-check it?'\\n\"\n", " ' System action: {{ \"actions\": [\"fact-checking\"] }}\\n'\n", " \"- User input: 'Could you tell me the tone conveyed by this article?'\\n\"\n", " ' System action: {{ \"actions\": [\"tone-analysis\"] }}\\n'\n", " \"- User input: 'Identify any key quotes in this article that stand out.'\\n\"\n", " ' System action: {{ \"actions\": [\"quote-extraction\"] }}\\n'\n", " \"- User input: 'Can you check the grammar and point out any bias in this article?'\\n\"\n", " ' System action: {{ \"actions\": [\"grammar-and-bias-review\"] }}\\n'\n", " \"- User input: 'Please provide a comprehensive analysis, including all aspects.'\\n\"\n", " ' System action: {{ \"actions\": [\"summarization\", \"fact-checking\", \"tone-analysis\", \"quote-extraction\", \"grammar-and-bias-review\"] }}\\n'\n", " \"- User input: 'I want a tone analysis and quote extraction, please.'\\n\"\n", " ' System action: {{ \"actions\": [\"tone-analysis\", \"quote-extraction\"] }}\\n'\n", " \"- User input: 'I have another question that’s not related to these functions.'\\n\"\n", " ' System action: {{ \"actions\": [\"invalid\"] }}\\n\\n'\n", " \"Input text:\\n{input_text}\"\n", " )\n", ")\n", "\n", "\n", "action_pipeline = action_prompt | llm.with_structured_output(SystemAction)\n", "\n", "\n", "\n", "def get_user_actions(input_text: str) -> List[str]:\n", " \"\"\"\n", " Identify the user's intended actions based on their input.\n", " \"\"\"\n", " system_actions = action_pipeline.invoke({\"input_text\": input_text})\n", " \n", " return system_actions[\"actions\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sample usage" ] }, { "cell_type": "code", "execution_count": 91, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "User input: 'Can you summarize this article for me?'\n", "System actions: ['summarization']\n", "\n", "\n", "User input: 'I'm not sure about the accuracy of this article. Can you summarize it and fact-check it?'\n", "System actions: ['summarization', 'fact-checking']\n", "\n", "\n", "User input: 'I want to know the tone of this article.'\n", "System actions: ['tone-analysis']\n", "\n", "\n", "User input: 'Can you extract any quotes from this article?'\n", "System actions: ['quote-extraction']\n", "\n", "\n", "User input: 'I need a review of this article for grammar and bias.'\n", "System actions: ['grammar-and-bias-review']\n", "\n", "\n", "User input: 'Can you provide a full report on this article?'\n", "System actions: ['summarization', 'fact-checking', 'tone-analysis', 'quote-extraction', 'grammar-and-bias-review']\n", "\n", "\n", "User input: 'I would like to know everything about this article.'\n", "System actions: ['summarization', 'fact-checking', 'tone-analysis', 'quote-extraction', 'grammar-and-bias-review']\n", "\n", "\n", "User input: 'I need help with something else.'\n", "System actions: ['no-action-required']\n", "\n", "\n", "User input: 'Ok, that's enough for now.'\n", "System actions: ['no-action-required']\n", "\n", "\n", "User input: 'What is the weather like today?'\n", "System actions: ['invalid']\n" ] } ], "source": [ "user_inputs = [\n", " \"Can you summarize this article for me?\",\n", " \"I'm not sure about the accuracy of this article. Can you summarize it and fact-check it?\",\n", " \"I want to know the tone of this article.\",\n", " \"Can you extract any quotes from this article?\",\n", " \"I need a review of this article for grammar and bias.\",\n", " \"Can you provide a full report on this article?\",\n", " \"I would like to know everything about this article.\",\n", " \"I need help with something else.\",\n", " \"Ok, that's enough for now.\",\n", " \"What is the weather like today?\",\n", "]\n", "\n", "for user_input in user_inputs:\n", " print(f\"\\n\\nUser input: '{user_input}'\")\n", " user_actions = get_user_actions(user_input)\n", " print(f\"System actions: {user_actions}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Define Node Functions\n", "\n", "The following functions are defined to handle various actions such as summarization, fact-checking, tone analysis, quote extraction, and grammar and bias review. These functions utilize the previously defined pipelines and helper functions to process the article text and return the desired results.\n", "\n", "- `get_or_create_chunks(state: State)`: Splits the article text into manageable chunks if not already done.\n", "- `categorize_user_input(state: State)`: Categorizes the user input into specific actions based on keywords.\n", "- `summarization_node(state: State)`: Handles the summarization action.\n", "- `fact_checking_node(state: State)`: Handles the fact-checking action.\n", "- `tone_analysis_node(state: State)`: Handles the tone analysis action.\n", "- `quote_extraction_node(state: State)`: Handles the quote extraction action.\n", "- `grammar_and_bias_review_node(state: State)`: Handles the grammar and bias review action." ] }, { "cell_type": "code", "execution_count": 92, "metadata": {}, "outputs": [], "source": [ "def get_or_create_chunks(state: State):\n", " \"\"\"\n", " This function gets the article text from the state and splits it into manageable chunks.\n", " The chunks are stored in the state to avoid recomputing them multiple times.\n", " \"\"\"\n", " article_text = state[\"article_text\"]\n", " chunks = state.get(\"chunks\", [])\n", " if not chunks:\n", " chunks = chunk_large_text(article_text)\n", " state[\"chunks\"] = chunks\n", "\n", " return chunks\n", "\n", "\n", "def categorize_user_input(state: State) -> State:\n", " \"\"\"\n", " This node handles the categorization of the user input to identify the intended actions.\n", " \"\"\"\n", " query = state[\"current_query\"]\n", " actions = get_user_actions(query)\n", " return {\"actions\": actions}\n", "\n", "\n", "def summarization_node(state):\n", " \"\"\"\n", " This node generates a summary of the article text.\n", " \"\"\"\n", " chunks = get_or_create_chunks(state)\n", " article_text = state[\"article_text\"]\n", " summary_result = summarize_article(article_text, chunks)\n", " return {\"summary_result\": summary_result}\n", "\n", "\n", "def fact_checking_node(state: State) -> State:\n", " chunks = get_or_create_chunks(state)\n", " article_text = state[\"article_text\"]\n", " fact_checking_results = fact_check_article(article_text, chunks)\n", " return {\"fact_check_result\": fact_checking_results}\n", "\n", "\n", "def tone_analysis_node(state: State) -> State:\n", " chunks = get_or_create_chunks(state)\n", " article_text = state[\"article_text\"]\n", " tone_analysis_results = tone_analysis_article(article_text, chunks)\n", " return {\"tone_analysis_result\": tone_analysis_results}\n", "\n", "\n", "def quote_extraction_node(state: State) -> State:\n", " chunks = get_or_create_chunks(state)\n", " article_text = state[\"article_text\"]\n", " quote_extraction_results = quote_extraction_article(article_text, chunks)\n", " return {\"quote_extraction_result\": quote_extraction_results}\n", "\n", "\n", "def grammar_and_bias_review_node(state: State) -> State:\n", " chunks = get_or_create_chunks(state)\n", " article_text = state[\"article_text\"]\n", " grammar_and_bias_review_results = (\n", " grammary_and_bias_analysis_article(article_text, chunks)\n", " )\n", " return {\"grammar_and_bias_review_result\": grammar_and_bias_review_results}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Routing function\n", "\n", "The `route` function determines the next action based on the current state. It checks the actions specified in the state and returns the corresponding routes. If no specific actions are found, it returns `END`.\n" ] }, { "cell_type": "code", "execution_count": 93, "metadata": {}, "outputs": [], "source": [ "def route(state: State) -> str:\n", " routes = []\n", " actions = state.get(\"actions\", [])\n", " if \"full report\" in actions:\n", " routes = routes.values()\n", " routes.pop(\"no-action-required\")\n", " routes.pop(\"invalid\")\n", " else:\n", " for action in actions:\n", " if action in routes:\n", " routes.append(routes[action])\n", "\n", " if not routes:\n", " return END\n", "\n", " return routes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create and Configure the Graph\n", "Here we set up the LangGraph, defining nodes and edges to create our workflow." ] }, { "cell_type": "code", "execution_count": 94, "metadata": {}, "outputs": [], "source": [ "# Define constants for repeated literals\n", "CATEGORY = \"category\"\n", "SUMMARY = \"summary\"\n", "FACT_CHECKING = \"fact checking\"\n", "TONE_ANALYSIS = \"tone analysis\"\n", "QUOTE_EXTRACTION = \"quote extraction\"\n", "GRAMMAR_AND_BIAS_REVIEW = \"grammar and bias review\"\n", "\n", "# Create a graph\n", "workflow = StateGraph(State)\n", "\n", "# Define the nodes\n", "workflow.add_node(CATEGORY, categorize_user_input)\n", "workflow.add_node(SUMMARY, summarization_node)\n", "workflow.add_node(FACT_CHECKING, fact_checking_node)\n", "workflow.add_node(TONE_ANALYSIS, tone_analysis_node)\n", "workflow.add_node(QUOTE_EXTRACTION, quote_extraction_node)\n", "workflow.add_node(GRAMMAR_AND_BIAS_REVIEW, grammar_and_bias_review_node)\n", "\n", "workflow.set_entry_point(CATEGORY)\n", "\n", "workflow.add_conditional_edges(\n", " CATEGORY,\n", " lambda state: state[\"actions\"],\n", " {\n", " \"summarization\": SUMMARY,\n", " \"fact-checking\": FACT_CHECKING,\n", " \"tone-analysis\": TONE_ANALYSIS,\n", " \"quote-extraction\": QUOTE_EXTRACTION,\n", " \"grammar-and-bias-review\": GRAMMAR_AND_BIAS_REVIEW,\n", " \"no-action-required\": END,\n", " \"invalid\": END,\n", " }\n", ")\n", "\n", "workflow.add_edge(SUMMARY, END)\n", "workflow.add_edge(FACT_CHECKING, END)\n", "workflow.add_edge(TONE_ANALYSIS, END)\n", "workflow.add_edge(QUOTE_EXTRACTION, END)\n", "workflow.add_edge(GRAMMAR_AND_BIAS_REVIEW, END)\n", "\n", "# Define the graph\n", "journalist_assistant = workflow.compile()" ] }, { "cell_type": "code", "execution_count": 95, "metadata": {}, "outputs": [ { "data": { "image/png": "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9q5du0xNTclk8p07d3bv3q2hoeHu7p6Wlubv7z9z5kwpKan09PQevvO3YW9vf/LkSUfHn0GGxMXFVVVV79+/Lycn19zcHBUVhcfjy8vL4cngioqKN2/eFBcXd3BwYH2JnJycra1tUlISlUodN25cbW1tbm6upKQkmzS7oL/W5WRl5bZt3Vf6rnjXbv+8vKchh05PnGged/MqDLu055+DTCbzVNjhyKhzEhKSw5VU4ABx27YAISHh4ycOJibdgc4GrKioqB47cl5DfVRk1LlTpw5XVH436GrRgJWVf637c8Wasg/vDocG3r13c7K5layMHFx5OHQwzG7ajAcp90KP7n+WmzVl8tTOq3w+y1c3NzdhUea7hUgkGo+beDkyfO++rXv2bvHftGqul1N5+S/EouEZjAzHZ+dk7N23de++rdt2rPea75KcfLfLOxcv+nO8semx4weOHg8eZzRh146gmtofBYXPu7z5v7B9a4CggOA/ezbHXLv8559r589bkpR0B1sG4cCUydYL5i+9dfvavn1b22ntJ45fVFFRZV3c6BLfVRtnzXTPy3928lTIq9f/qquPYnenioqquZnl5cjwc+dPioiIhhwM67KHYye/jvaY129ehoQGvC0tXr9u65gx+j3JDXZNA6KqqmY8bsI09iErJptbBe4L5SPynTh56FJkuLy8oh5LsDJzM0uzSRYKCorwXx3t0UaG44emgyXsPvcHHJ1kOiUxMf74iYNfvn7CZgGIROKuncEEInHjppVnzh5dMH8ZNoGlpKQSvP94e3t78IHdMbGXbW0c/9l9EA6Uhw9TWr9u6+fPH4+fOJie/gBeOX70/OjRelHR50+cPFRXT7aZ6tClMF2WO41GCz6wW05OwWvuYgDAyJHqS5esvB1//cmTdBh4c9ZM9/RHD86EH3v1+t8uX8uucnLQtD7L/QwNjC9HhkdHX/j67XOHF3L4fARk2DCldir1VNjhu/duzZ49BwYNdrB3dv9j3tWYS+s3rKiuruo/w6kXeltXZyw0APpJpIFP50FOL+o5Hx/fgaATM6bPSktLCjkckF/wzNnJrcutkr3o8jgU69IlKyebWyUmxsO1NQCAjvYYAMBU6/87gK7bzqh3vdVAZvjw4Xv27Glvbw8NDY2Li7Oystq2bVuXJSjz/9q777im7vUP4CcJBJJASCAJhB02QpkFnKiI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", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "display(\n", " Image(\n", " journalist_assistant.get_graph().draw_mermaid_png()\n", " )\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sample usage" ] }, { "cell_type": "code", "execution_count": 96, "metadata": {}, "outputs": [], "source": [ "full_report = journalist_assistant.invoke(\n", " {\n", " \"current_query\": \"Can you provide a full report on this article?\",\n", " \"article_text\": full_article_text,\n", " }\n", ")" ] }, { "cell_type": "code", "execution_count": 97, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[{'explanation': 'The halcyon bird is indeed celebrated in folklore as a '\n", " 'symbol of tranquility, particularly in relation to the '\n", " 'calming of winds and seas during its nesting period.',\n", " 'statement': 'The halcyon bird is a symbol of tranquility and mythical '\n", " 'wonder.',\n", " 'status': 'confirmed',\n", " 'suggested_keywords': []},\n", " {'explanation': \"The term 'halcyon' originates from the Greek myth of \"\n", " 'Alcyone, who was transformed into a kingfisher, which is '\n", " 'well-documented in classical literature.',\n", " 'statement': \"The term 'halcyon' is historically rooted in the Greek myth of \"\n", " 'Alcyone, a figure transformed into a kingfisher.',\n", " 'status': 'confirmed',\n", " 'suggested_keywords': []},\n", " {'explanation': 'The halcyon kingfisher refers to certain species of '\n", " 'kingfishers, particularly those in tropical and subtropical '\n", " 'regions, which are recognized in ornithology.',\n", " 'statement': 'The halcyon kingfisher is a real species.',\n", " 'status': 'confirmed',\n", " 'suggested_keywords': []},\n", " {'explanation': 'Modern science dismisses the belief that halcyon birds can '\n", " 'calm the sea, attributing such phenomena to seasonal weather '\n", " 'patterns instead.',\n", " 'statement': 'Halcyon birds possess the ability to calm the sea.',\n", " 'status': 'refuted',\n", " 'suggested_keywords': []},\n", " {'explanation': 'While Captain Hartley recounts this anecdote, there is no '\n", " 'documentation or physical evidence to support it, making it '\n", " 'unverifiable.',\n", " 'search_results': [[{'summary': 'The halcyon bird, often associated with '\n", " 'serenity and resilience, has gained '\n", " 'prominence in modern culture as a symbol of '\n", " 'stability during turbulent times, according '\n", " 'to cultural historian Dr. Stephen Archer. '\n", " 'Its representation in films, literature, '\n", " 'and music reflects a societal yearning for '\n", " 'harmony amidst chaos. Beyond its cultural '\n", " 'significance, the halcyon bird also plays a '\n", " 'role in scientific research, particularly '\n", " 'in biomimicry. Dr. Marquez highlights that '\n", " 'the anatomy of kingfisher species has '\n", " 'inspired innovations in transportation, '\n", " 'such as the design of high-speed trains, '\n", " \"showcasing nature's potential solutions to \"\n", " 'human challenges. Despite many of its '\n", " 'legends being debunked, the halcyon bird '\n", " 'continues to inspire curiosity and wonder '\n", " 'across generations. It serves as a bridge '\n", " 'between the scientific understanding of '\n", " 'nature and the imaginative narratives that '\n", " 'enrich human experience. The ongoing '\n", " \"exploration of the halcyon bird's \"\n", " 'ecological role and its influence on art '\n", " 'and technology underscores its enduring '\n", " 'symbolic value. This convergence of myth, '\n", " 'culture, and science suggests that the '\n", " 'halcyon bird will remain a source of '\n", " 'inspiration for years to come.',\n", " 'title': 'Henry Hartley - Hall of Valor: Medal of '\n", " 'Honor, Silver Star, U.S ...',\n", " 'url': 'https://valor.militarytimes.com/recipient/recipient-19556/'}],\n", " [{'summary': 'The halcyon bird, often associated with '\n", " 'serenity and resilience, has gained '\n", " 'prominence in modern culture as a symbol of '\n", " 'stability during turbulent times, according '\n", " 'to cultural historian Dr. Stephen Archer. '\n", " 'Its representation in films, literature, '\n", " 'and music reflects a societal yearning for '\n", " 'harmony amidst chaos. Beyond its cultural '\n", " 'significance, the halcyon bird, '\n", " 'particularly the kingfisher species, is '\n", " 'also a subject of scientific study. '\n", " 'Researchers, including Dr. Marquez, are '\n", " 'exploring its streamlined anatomy for '\n", " 'potential applications in transportation '\n", " 'and engineering, noting that the '\n", " \"kingfisher's beak has already inspired \"\n", " 'designs for high-speed trains. This '\n", " 'intersection of myth, culture, and science '\n", " 'highlights the enduring symbolic value of '\n", " 'the halcyon bird, which continues to '\n", " 'inspire curiosity and innovation. Despite '\n", " 'the debunking of many associated legends, '\n", " 'the bird remains a bridge between '\n", " \"humanity's quest for understanding and \"\n", " \"appreciation for nature's mysteries. The \"\n", " 'ongoing exploration of its ecological role '\n", " 'and influence on art and technology '\n", " 'suggests that the halcyon bird will '\n", " 'continue to captivate future generations.',\n", " 'title': 'The Myth of Halcyon - Halcyon Days - Greek '\n", " 'Myths & Greek Mythology',\n", " 'url': 'https://www.greekmyths-greekmythology.com/the-myth-of-halcyon-the-halcyon-days/'}],\n", " [{'summary': 'The halcyon bird, often associated with '\n", " 'serenity and resilience, has gained '\n", " 'prominence in modern culture as a symbol of '\n", " 'stability during turbulent times, according '\n", " 'to cultural historian Dr. Stephen Archer. '\n", " 'Its representation in films, literature, '\n", " 'and music reflects a societal yearning for '\n", " 'harmony amidst chaos. Beyond its cultural '\n", " 'significance, the halcyon bird, '\n", " 'particularly the kingfisher species, is '\n", " 'also a subject of scientific study in '\n", " 'biomimicry. Dr. Marquez highlights that the '\n", " \"kingfisher's streamlined beak has inspired \"\n", " 'innovations in transportation, such as the '\n", " 'design of high-speed trains. This '\n", " 'intersection of myth, culture, and science '\n", " 'underscores the enduring symbolic value of '\n", " 'the halcyon bird, which continues to '\n", " 'inspire curiosity and wonder across '\n", " 'generations. Despite many associated '\n", " 'legends being debunked, the bird remains a '\n", " \"bridge between humanity's quest for \"\n", " \"understanding and appreciation for nature's \"\n", " 'mysteries. The ongoing exploration of its '\n", " 'ecological role and influence on art and '\n", " 'technology suggests that the halcyon bird '\n", " 'will continue to captivate interest in the '\n", " 'years to come.',\n", " 'title': 'RMS Campania - Wikipedia',\n", " 'url': 'https://en.wikipedia.org/wiki/RMS_Campania'}]],\n", " 'statement': \"Captain Ed Hartley shared his family’s tale of 'a radiant bird \"\n", " \"guiding their ship to calm waters in 1892.'\",\n", " 'status': 'unverifiable',\n", " 'suggested_keywords': ['Captain Ed Hartley',\n", " 'halcyon bird anecdote',\n", " '1892 ship story']},\n", " {'explanation': 'This speculation lacks empirical research to support it, and '\n", " 'further studies would be needed to confirm or refute the '\n", " 'claim.',\n", " 'search_results': [[{'summary': 'The halcyon bird, often associated with '\n", " 'serenity and resilience, has gained '\n", " 'prominence in modern culture as a symbol of '\n", " 'stability during turbulent times, according '\n", " 'to cultural historian Dr. Stephen Archer. '\n", " 'Its representation in films, literature, '\n", " 'and music reflects a societal yearning for '\n", " 'harmony amidst chaos. Beyond its cultural '\n", " 'significance, the halcyon bird, '\n", " 'particularly the kingfisher species, is '\n", " 'also a subject of scientific interest. '\n", " 'Researchers, including Dr. Marquez, are '\n", " 'studying its streamlined anatomy for '\n", " 'potential applications in transportation '\n", " 'and engineering, noting that the '\n", " \"kingfisher's beak has already inspired \"\n", " 'designs for high-speed trains. This '\n", " 'intersection of myth, culture, and science '\n", " 'highlights the enduring symbolic value of '\n", " 'the halcyon bird, which continues to '\n", " 'inspire curiosity and innovation. Despite '\n", " 'many of its associated legends being '\n", " 'debunked, the bird remains a bridge between '\n", " \"humanity's quest for understanding and \"\n", " \"appreciation for nature's mysteries. The \"\n", " 'ongoing exploration of its ecological role '\n", " 'and influence on art and technology '\n", " 'suggests that the halcyon bird will '\n", " 'continue to captivate future generations.',\n", " 'title': 'Unravelling the enigma of bird '\n", " 'magnetoreception - Nature',\n", " 'url': 'https://www.nature.com/articles/d41586-021-01596-6'}],\n", " [{'summary': 'The halcyon bird, often associated with '\n", " 'serenity and resilience, has gained '\n", " 'prominence in modern culture as a symbol of '\n", " 'stability during turbulent times, according '\n", " 'to cultural historian Dr. Stephen Archer. '\n", " 'Its representation in films, literature, '\n", " 'and music reflects a societal yearning for '\n", " 'harmony amidst chaos. Beyond its cultural '\n", " 'significance, the halcyon bird also plays a '\n", " 'role in scientific research, particularly '\n", " 'in biomimicry. Dr. Marquez highlights that '\n", " 'the anatomy of kingfisher species has '\n", " 'inspired innovations in transportation, '\n", " 'such as the design of high-speed trains, '\n", " \"showcasing nature's potential solutions to \"\n", " 'human challenges. Despite many of its '\n", " 'legends being debunked, the halcyon bird '\n", " 'continues to inspire curiosity and wonder '\n", " 'across generations. It serves as a bridge '\n", " 'between the quest for understanding and the '\n", " 'appreciation of mystery, embodying a blend '\n", " 'of myth, culture, and science. The ongoing '\n", " 'exploration of its ecological role and '\n", " 'influence on art and technology suggests '\n", " 'that the halcyon bird will remain a '\n", " 'significant source of inspiration in the '\n", " 'future.',\n", " 'title': 'Avian Navigation | Ornithology - Oxford '\n", " 'Academic',\n", " 'url': 'https://academic.oup.com/auk/article/126/4/717/5148354'}],\n", " [{'summary': 'The halcyon bird, often associated with '\n", " 'serenity and resilience, has gained '\n", " 'prominence in modern culture as a symbol of '\n", " 'stability during turbulent times, according '\n", " 'to cultural historian Dr. Stephen Archer. '\n", " 'Its representation in films, literature, '\n", " 'and music reflects a societal yearning for '\n", " 'harmony amidst chaos. Beyond its cultural '\n", " 'significance, the halcyon bird also plays a '\n", " 'role in scientific research, particularly '\n", " 'in biomimicry. Dr. Marquez highlights that '\n", " 'the anatomy of kingfisher species has '\n", " 'inspired innovations in transportation, '\n", " 'such as the design of high-speed trains, '\n", " \"showcasing nature's potential to address \"\n", " 'human challenges. Despite many of its '\n", " 'legends being debunked, the halcyon bird '\n", " 'continues to inspire curiosity and wonder '\n", " 'across generations. It serves as a bridge '\n", " 'between the realms of myth, culture, and '\n", " 'science, emphasizing the balance between '\n", " 'scientific inquiry and imaginative '\n", " 'exploration. The ongoing fascination with '\n", " 'the halcyon bird underscores its enduring '\n", " 'symbolic value in both ecological and '\n", " 'artistic contexts.',\n", " 'title': 'Migratory Birds | U.S. Fish & Wildlife '\n", " 'Service - U.S. Fish and Wildlife ...',\n", " 'url': 'https://www.fws.gov/program/migratory-birds'}]],\n", " 'statement': 'Halcyon birds may possess magnetic sensitivity, akin to that '\n", " 'of migratory birds.',\n", " 'status': 'unverifiable',\n", " 'suggested_keywords': ['halcyon bird magnetic sensitivity',\n", " 'avian navigation',\n", " 'migratory birds']},\n", " {'explanation': 'The text presents differing views among conservationists '\n", " 'regarding the focus of conservation efforts, which is a '\n", " 'common debate in environmental discussions.',\n", " 'statement': 'There is disagreement among conservationists over whether '\n", " 'efforts should focus on protecting habitats linked to the '\n", " 'halcyon bird.',\n", " 'status': 'confirmed',\n", " 'suggested_keywords': []},\n", " {'explanation': 'This statement reflects a widely accepted view in cultural '\n", " 'studies that myths can promote environmental stewardship.',\n", " 'statement': 'Myths about animals have historically served as a way to '\n", " 'foster respect and care for the natural world.',\n", " 'status': 'confirmed',\n", " 'suggested_keywords': []},\n", " {'explanation': 'The design of high-speed trains has indeed been inspired by '\n", " 'the streamlined anatomy of kingfishers, a fact supported by '\n", " 'biomimicry research.',\n", " 'statement': 'The kingfisher’s beak has already influenced the design of '\n", " 'high-speed trains.',\n", " 'status': 'confirmed',\n", " 'suggested_keywords': []},\n", " {'explanation': 'The halcyon bird is often discussed in the context of its '\n", " 'mythological significance, cultural impact, and scientific '\n", " 'relevance, making this statement accurate.',\n", " 'statement': 'The halcyon bird represents a fascinating convergence of myth, '\n", " 'culture, and science.',\n", " 'status': 'confirmed',\n", " 'suggested_keywords': []}]\n" ] } ], "source": [ "pprint.pprint(full_report[\"fact_check_result\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Report Generation\n", "Format the results in markdown and save them to be easier to read.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def format_analysis_results(report_data):\n", " # Parsing and formatting each section, handling missing values as needed.\n", "\n", " # Actions Performed\n", " actions = report_data.get('actions', [])\n", " actions_text = \"\\n\".join(\n", " f\"- {action}\" for action in actions) if actions else \"No actions recorded.\"\n", "\n", " # Current Query\n", " current_query = report_data.get('current_query', 'No query provided.')\n", "\n", " # Summary\n", " summary = report_data.get('summary_result', 'No summary available.')\n", "\n", " # Fact-check Results\n", " fact_check_result = report_data.get('fact_check_result', [])\n", " fact_check_text = \"\"\n", " if fact_check_result:\n", " for fact in fact_check_result:\n", " statement = fact.get('statement', 'No statement provided')\n", " status = fact.get('status', 'Unknown')\n", " explanation = fact.get('explanation', 'No explanation provided')\n", " fact_check_text += f\"\\n- **Statement:** {statement}\\n - **Status:** {status}\\n - **Explanation:** {explanation}\\n\"\n", "\n", " # Adding search results\n", " search_results = fact.get('search_results', [])\n", " if search_results:\n", " fact_check_text += \" - **Related Search Results:**\\n\"\n", " for result_set in search_results:\n", " for result in result_set:\n", " title = result.get('title', 'No title available')\n", " summary = result.get(\n", " 'summary', 'No summary available').strip().replace('\\n', ' ')\n", " url = result.get('url', 'No URL available')\n", " fact_check_text += f\" - **Title:** {title}\\n\\n - **Summary:** {summary}\\n\\n - **URL:** {url}\\n\\n\"\n", " else:\n", " fact_check_text = \"No fact-check results available.\"\n", "\n", " # Grammar and Bias Review\n", " grammar_review = report_data.get('grammar_and_bias_review_result', ['No grammar or bias review available.'])\n", " grammar_review = \"\\n\".join(grammar_review)\n", " \n", " # Tone Analysis\n", " tone_analysis = report_data.get('tone_analysis_result', ['No tone analysis available.'])\n", " tone_analysis = \"\\n\".join(tone_analysis)\n", "\n", " # Quotes Extracted\n", " quote_extraction = report_data.get('quote_extraction_result', ['No quotes extracted.'])\n", " quote_extraction = \"\\n\".join(quote_extraction)\n", "\n", " # Format the report\n", " report_structured = f\"\"\"\n", "# Report on Article Analysis\n", "\n", "## Query\n", "{current_query}\n", "\n", "## Actions Performed\n", "{actions_text}\n", "\n", "## Summary\n", "{summary if summary else \"No summary available.\"}\n", "\n", "## Fact-check Results\n", "{fact_check_text if fact_check_text else \"No fact-check results available.\"}\n", "\n", "## Grammar and Bias Review\n", "{grammar_review if grammar_review else \"No grammar or bias review available.\"}\n", "\n", "## Tone Analysis\n", "{tone_analysis if tone_analysis else \"No tone analysis available.\"}\n", "\n", "## Quotes Extracted\n", "{quote_extraction if quote_extraction else \"No quotes found.\"}\n", "\n", "---\n", "\n", "\n", "## Generated by your reliable AI assistant 🤖\n", "\"\"\"\n", "\n", " return report_structured" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "report_content = format_analysis_results(full_report)\n", "\n", "# Save the report to a ArticelAnalysis.md file\n", "report_path = data_path / \"ArticleAnalysis.md\"\n", "with open(report_path, \"w\") as report_file:\n", " report_file.write(report_content)\n", "\n", "print(f\"Report saved to {report_path} file\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Additional Considerations\n", "\n", "While this Journalism-Focused AI Assistant provides powerful tools for enhancing journalistic practices, it’s important to acknowledge its limitations, potential areas for improvement, and specific scenarios where it excels.\n", "\n", "### Limitations\n", "- **Accuracy of Sources**: The reliability of fact-checking depends heavily on the quality and credibility of the sources retrieved via web searches. Results may vary depending on the availability of accurate information.\n", "- **Bias in AI Outputs**: While tone and bias detection modules help mitigate issues, AI models themselves may occasionally introduce or miss biases due to training data limitations.\n", "- **Real-Time Updates**: The system is not connected to live news feeds or continuously updating databases, which might limit its utility for breaking news scenarios.\n", "\n", "### Potential Improvements\n", "- **Real-Time Data Integration**: Connecting to live news feeds or APIs for real-time updates could enhance the relevance and timeliness of analyses.\n", "- **Fine-Tuned Models**: Custom training or fine-tuning the AI models on journalistic datasets could improve the system’s understanding of nuances in reporting.\n", "- **Enhanced Workflows**: Additional workflows for niche journalism use cases, such as investigative reporting or legal compliance, could increase flexibility.\n", "\n", "### Specific Use Cases\n", "- **Fact-Checking at Scale**: Ideal for verifying multiple claims in investigative journalism or research-heavy articles.\n", "- **Tone and Bias Monitoring**: Helpful for ensuring balanced coverage and maintaining editorial neutrality.\n", "- **Content Summarization**: Streamlines the review process for lengthy reports, interviews, or documents.\n", "- **Transparent Reporting**: Quote extraction and grammar review make it easier to present clear and credible content.\n", "\n", "By addressing these considerations, the tool can continue to evolve, becoming an even more indispensable resource for modern journalism.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](https://europe-west1-genai-agents-views-tracker.cloudfunctions.net/genai-agents-tracker?notebook=all-agents-tutorials--journalism-focused-ai-assistant-langgraph)" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12" } }, "nbformat": 4, "nbformat_minor": 2 }