From 9b183461f9a48ce0d4e833eacdd265bea2ff5376 Mon Sep 17 00:00:00 2001 From: xpander-ai-coding-agent Date: Thu, 7 Aug 2025 09:34:26 +0000 Subject: [PATCH] Refactor AI News Generator to use CrewAI Flows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace traditional Crew orchestration with Flow-based approach - Implement NewsGeneratorFlow with @start and @listen decorators - Add modular two-phase workflow: research phase and writing phase - Create structured state management with Pydantic models - Add CLI interface through main.py for command-line usage - Update Streamlit app to use new Flow implementation - Enhance README with comprehensive Flow-based architecture documentation - Maintain backward compatibility with existing Streamlit interface πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- ai_news_generator/README.md | 151 +++++++++++++- .../__pycache__/app.cpython-311.pyc | Bin 0 -> 3660 bytes .../__pycache__/main.cpython-311.pyc | Bin 0 -> 4392 bytes .../__pycache__/news_flow.cpython-311.pyc | Bin 0 -> 9311 bytes ai_news_generator/app.py | 102 +-------- ai_news_generator/main.py | 98 +++++++++ ai_news_generator/news_flow.py | 193 ++++++++++++++++++ 7 files changed, 443 insertions(+), 101 deletions(-) create mode 100644 ai_news_generator/__pycache__/app.cpython-311.pyc create mode 100644 ai_news_generator/__pycache__/main.cpython-311.pyc create mode 100644 ai_news_generator/__pycache__/news_flow.cpython-311.pyc create mode 100644 ai_news_generator/main.py create mode 100644 ai_news_generator/news_flow.py diff --git a/ai_news_generator/README.md b/ai_news_generator/README.md index 6bc3e25b6..2a845f947 100644 --- a/ai_news_generator/README.md +++ b/ai_news_generator/README.md @@ -1,7 +1,41 @@ -# AI News generator +# AI News Generator -This project leverages CrewAI and Cohere's Command-R:7B model to build an AI news generator! +This project leverages **CrewAI Flows** and Cohere's Command-R:7B model to build a modular, agentic AI news generator! + +The application has been refactored to use CrewAI's new Flow-based architecture, providing better modularity, state management, and workflow orchestration. + +## ✨ Features + +- **Flow-Based Architecture**: Built using CrewAI Flows with `@start` and `@listen` decorators +- **Two-Phase Workflow**: Research phase followed by content writing phase +- **Modular Design**: Separate agents for research and content writing +- **State Management**: Proper state handling between workflow phases +- **Multiple Interfaces**: Both CLI and Streamlit web interface +- **Structured Output**: Well-formatted markdown blog posts with citations + +## πŸ—οΈ Architecture + +The application uses a **Flow-based architecture** with two main phases: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” @start β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Research Phase │──────────────▢│ Writing Phase β”‚ +β”‚ β”‚ @listen β”‚ β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β€’ Web search β”‚ β”‚ β€’ Content β”‚ +β”‚ β€’ Fact checking β”‚ β”‚ generation β”‚ +β”‚ β€’ Source β”‚ β”‚ β€’ Formatting β”‚ +β”‚ validation β”‚ β”‚ β€’ Citations β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Core Components + +- **`NewsGeneratorFlow`**: Main Flow class orchestrating the workflow +- **Research Agent**: Senior Research Analyst for comprehensive research +- **Writing Agent**: Content Writer for transforming research into engaging content +- **State Management**: Pydantic models for structured data flow ## Installation and setup @@ -9,13 +43,122 @@ This project leverages CrewAI and Cohere's Command-R:7B model to build an AI new - [Serper API Key](https://serper.dev/) - [Cohere API Key](https://dashboard.cohere.com/api-keys) - **Install Dependencies**: Ensure you have Python 3.11 or later installed. ```bash - pip install crewai crewai-tools + pip install crewai crewai-tools streamlit python-dotenv + ``` + +**Environment Setup**: + Create a `.env` file in the project directory: + ```env + SERPER_API_KEY=your_serper_api_key_here + COHERE_API_KEY=your_cohere_api_key_here ``` +## πŸš€ Usage + +### Command Line Interface + +Run the flow directly from the command line: + +```bash +# Basic usage +python main.py --topic "Latest developments in artificial intelligence" + +# Save to file +python main.py --topic "Climate change solutions" --output article.md + +# Verbose mode +python main.py --topic "Blockchain innovations" --verbose +``` + +### Streamlit Web Interface + +Launch the interactive web interface: + +```bash +streamlit run app.py +``` + +Then open your browser to the displayed URL (typically `http://localhost:8501`). + +### Programmatic Usage + +Use the flow in your own Python code: + +```python +from news_flow import NewsGeneratorFlow + +# Create and run the flow +flow = NewsGeneratorFlow() +result = flow.kickoff(inputs={"topic": "Your topic here"}) +print(result) + +# Or use the convenience function +from news_flow import generate_content_with_flow + +content = generate_content_with_flow("Your topic here") +print(content) +``` + +## πŸ”§ Flow Implementation Details + +### Flow Structure + +The `NewsGeneratorFlow` class implements the CrewAI Flow pattern: + +```python +class NewsGeneratorFlow(Flow[ResearchState]): + @start() + def research_phase(self) -> str: + # Initial research using Senior Research Analyst + + @listen(research_phase) + def writing_phase(self, research_results: str) -> str: + # Content writing using research results +``` + +### State Management + +The flow uses structured state management with Pydantic models: + +```python +class ResearchState(BaseModel): + topic: str + research_brief: str + sources: list[str] = [] + key_findings: list[str] = [] +``` + +### Agent Specialization + +- **Senior Research Analyst**: Handles web research, fact-checking, and source validation +- **Content Writer**: Transforms research into engaging, well-structured blog content + +## πŸ§ͺ Testing + +Test the basic functionality: + +```bash +# Test the flow with a simple topic +python main.py --topic "Python programming" --verbose + +# Test the web interface +streamlit run app.py +``` + +## πŸ“ Project Structure + +``` +ai_news_generator/ +β”œβ”€β”€ app.py # Streamlit web interface +β”œβ”€β”€ main.py # CLI entry point +β”œβ”€β”€ news_flow.py # Core Flow implementation +β”œβ”€β”€ README.md # This file +└── .env # Environment variables (create this) +``` + --- ## πŸ“¬ Stay Updated with Our Newsletter! diff --git a/ai_news_generator/__pycache__/app.cpython-311.pyc b/ai_news_generator/__pycache__/app.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e06cd54499619b6423aa32cfe54558abf5e18b4 GIT binary patch literal 3660 zcmb7FO>7&-6`ox#e^w+ViWDj9e{9*7h)A?-OK}p|P4#0rh8wxAjJBaG5G(FTT$$vu zyGzp|dSC$^bTCi@F`Be#5FZLPMs)DeIRD2ZHfe9-)8Ig*{+xj@SQnA@m6nk;r6G()uz<7S?Rm zmE)2eLwT;OJLgGy7{nst*@1&!HIE}?^4JZp%@8@!EqZpnJD~R~c&*Wt^4V+Ax5L0b zye<2-RY>I9_X)dUdPC)B>|6cK<4Ejimj_GPjexW77XxB&x3kr%WYCm4ytV6CEuD^* zkk~DT#mEi^w!>?UWLKM(o@2FyYC#;6K%G1n_9w-R9~wW$LB+9`&%U-QVjj= zuwWg$)?iTHKZ?#Hl`FCLT_2$m!=R4znLSBev?G*_=K4H&~ep|Ekao^EBdoGTMxD~a}ka+TQ zRy?&k+8S#>Gck%t|7oP0O%9m;3_4m)!+MAev|9Zc`kZa$Q2a$EIdEqn@3X!ori856 ze7^{`;dy-CIe3a^+U*dTWfVXAEp=e&31jrHlRQxbjil0YUL$!eRg$z!YAq|5;*8{kH5v=Q8j@Ja)~HJ#H*WlULhrr5b`3PmovZ}Q(= z=qdo9U@%m%4t*|ZS=qp6FuwRL?RJq4Nrzh>lw}P7qUW{ zWaO+!lyY=*bQI5u_k?^Je6{a3{I^N2K;uwU$U59=j^?EC{`#6Z{`G#@H90w14ms{O zeK5QQMal`v=54r^(>4plh!j;Tq=E$Tg`kRBrq~T?|FQM;zn}dEe;}8r{U9X_LPzev z&2c_~kH8=jRiZJmFqHDxM1kA~WECP%XYUc`=!FD0woz087>d=QLs(kFc|t@`G@b!S z0OTt;Co2#>(4s-);*OGw2|S;b(s%J#yOU$oUR4tm%@LkX;GYSShSy|>7~BcrJsJ}; zk{~aovxipBB=CY<%G1O)&nQGKX2Bmw84M8K6=4&vNk>kkoqsHVkN&&S_caFc3DS=2 z{R*|#8q^t6o;Qtke6iESN=IsQo|No-UxcEYToqKnNhlN)Sx9Hh(+a&1s?lwRL8VvX zHy#j46gG{Joq~1&E3{3YgIA^hD;4w~9}1j(Osf8gntwv)CSC&#+Q$$7(IgGrVBsV3}+oLCNOkTBubRzHp{>mDSglTR(?yp`w3OMy0H8H# z9q>2%n$LE{@T$=DRt1HYx&YN}@YGfCod^mA-|(nekWdW&k!mnJt3^$Nrr6~~uF3F` z`vq_XoTniK?FA_h#cO!195uRVZkiprdBCLzB|wulPQ$AZv(Fg()%$5u&?LBlA%@$8 z(r|&_PmmG?y-Ebg8{g%eEEcom2g)T_(v(s^hBRxq7>21MzGD9BD5l#_9Zl)2PaO^G ztxv;)m_Wt<>)=+f5~Pbrg{`5mK79V!Pye#0qva}EuAya^&`rL=ZuxZn!zx;;p(PzH z)sY`qem(HxD!Nrew{&z1%@wo;3&RBpmvM|3;< zU_g(KSNV92k2jFl4b}pw`y$)y1AhZ~qJcf+;e-2>z!Hed7BVIGsQA8f@K8nD?BL*_ zjbkm}vH>zt+M{^wJD?rH*ETb?wVbpBE_2Le139D#|iS5ODpcmqDLqBn@8knjDru4v6ll7^m8W^bsM)bf4 z4d2N2TrDtMkB!%(1CNsRp;KSn`Td>Wt^8r-??V^VS(B<0DWxfBp_0*%M z8@xN%xrc!4Q_^tAK_h@deGSMku+1j*Ko8jRfgZj1CH?*K$=kbc!vdaaV7eBV*1z)r zI0sM<{0#jLIF3@6_bAyvbpHR%Onh5TT40@`bpolreYJ|hv_eqWN&clOx>-Xvb^B2@ zD9&&CX5U-zx}Hb6=cnAyPY39HikqM2pHI8!{1QXw@6h=RFSp?LzUX8q9b>L_yI#a+ tCl`C!mod6{+3UW>x?c_sE?#lJe8){WS19Kd>w@{KZthyt`|1P(^nYtywY>lU literal 0 HcmV?d00001 diff --git a/ai_news_generator/__pycache__/main.cpython-311.pyc b/ai_news_generator/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71678eacb94fb6e7114401bf2c4a6ef901822db7 GIT binary patch literal 4392 zcmb^!T}&IvdDh;w*IwJ$U?2e!20}t;0++wjwFHn50g~$l(hIsOyjY8OfhFr*XLcRH zf{KT$L@KIscaaj^T~t){=vCq29&%D2+Ule>N!zD&WGU84NT*bJxTiYGMM8P%H*1V- z$VIJ`+3`2u%zS_IeKYfo{}2rN5tPT|=Jf9X`#0?r3%iXxp9AC;lF&Gk7|EqFLoBu$(o`gKK;4J_igo=AwA)Tt_(j6tkSCdIgnO&w1aiG? zG9#r~smiP_^=6!HPx~9;b@1Wc9X~S2tSO@tintWny+wVfLVk6*eV-wz=8k(eM+JR! z&+Pu9E`%yqvglfHtp!W7B}}R<*QziZb(K;V-R91rLug@_MT=~OMfH`u5J_exRMbV* zVZF`89>^-2llDpbHySGA`ZikR7Pz%=iKB!qu)8%$jr7|c2~9YJP9lSybt^@*xkBec^+aNEy^SNq!y`BtzGmkc-QKRqvdVoDWbcl5C>}4?uc}>rQh>oC{D7OOlfg? zZPR2xQqa6I**G`2k!Rk8LLiCrg#*kyqrX^cViYIgzFiqZ_!B^X4?@ zPC(l0O3vfXPE$`RaoqZzXv&6(CHcCn>Peby49HtpB&IT{#1&C>vY4tWkiX(`>uV$p zs7eBa;rO(uP085M)f8Q>(F)4+l$lJKj?}m+cO@ieJ=8TFZd-uV=_FF1wyPiygBVDh zvA8N4hV7A)imFegz1t>_rCY`v)2SO}Ll#MV8biv(vqS;y;soddp{zNdlx?2K*HQ|R zC7Yd=)nuA4nvy;_}yc6V@Nm6pUZcq~^K<4%w2HU6L{m|jEJ)#r;0(a9OAv)>Cg|);*P18+0A!9j_ zH0L3&v(&KfXo^mq1d#vjr%U*asb936y#i4Z>ukT7dD%=fA`Qg;z?^Cv@||0|&O0DN8LjGn?j8 z=49uO>5~L@hc1EmkyO2-_2a+pa8uVxU|2U!*Ep-jFhVV%ON7Cn`Fr1RNmmaKjtsuv zKQ?#{4~&kC4UUX;r!T!>CB1|uc*n{9C!enU=TE=Eqe=Q?blfDuX;D>J_ryFLPMM_W zo_TU-1#ic_qu%C4GL;kw?4M9SnM%>KcZqh1-T1yZd#-dS&coInDjo|vAWBjUSRDr2 zZJ5ONJ7VadL1-ScIT%%vHkTyu1_hdVI7fyl$%NRfJg1n1zRGOxRV98^pPaNkh4X2% zdQ#SG&N)f8_rj=Ss~!2NJU^j}M7luVvm})?ZQtNrTy{={?JXVxpGn}UJ0Zre+CE2b zOrDE(@V3V>4ir=KU|+-2*7m?AOsS^r-F^q#%%si8DKSP_UzIo?+MUA&4l~1;Xcc$ zTbnfv;KpE632ajGx4!HL0QKwmy%{Uqp9%M8!~KA4hHGx}w<8}3H-*)*4^(gEIgkVG z3qJ~0uXbhvI2*uL0B;5&R`rogpgkLCw*u|ZT|IK+;>yM4ixh3YF|snUJW@9LZB=;r z+SXHn+Qdalz=_hCznuOvM0zO04`ulwKys9N`P!xsrTY-bi@+gozTIzynxTFX|NV@` zLx4_Z`ICU;U^DfAY+q|G&}0>BE>Qb8(EKRSoC&mK11(mdWm`N|0Y=eP3ZMX5Lxbb= zx9a|P&iA1E{`#RK=zdGxklTI#odDnu-0YCo`@rj_xSyf8&_MCS4A4I~;vYWlesJQ{ zaJ&0qD+ePF+c_9{c)ULZoqzNke)nznKihm1cJ{t|!u_uk9N;z|i_y~@i*@*IZ!9M1 z@mP%1f*s@l0NVqp8`1-z|1)hLedfgIrQzIJ&J92XXktPJF8c4`kLWo-L};FT$@e65 zDW%GNq#L?4e;U6A09%n^@+`-&;8_46#Dtfs=$AwOCFjebMys;sP`g!G^PW+Lf#8%u w$%WX!a{7^bzvbSaV*^VAAD&+xTOPad!O902HkxIlRb%7 literal 0 HcmV?d00001 diff --git a/ai_news_generator/__pycache__/news_flow.cpython-311.pyc b/ai_news_generator/__pycache__/news_flow.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..baef6b8d2ea735412a0baf41dfb11f2aa9f3da42 GIT binary patch literal 9311 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 IA`bTd0Q!-=-T(jq literal 0 HcmV?d00001 diff --git a/ai_news_generator/app.py b/ai_news_generator/app.py index 7fc78e07d..c981fe462 100644 --- a/ai_news_generator/app.py +++ b/ai_news_generator/app.py @@ -1,7 +1,6 @@ import os import streamlit as st -from crewai import Agent, Task, Crew, LLM -from crewai_tools import SerperDevTool +from news_flow import generate_content_with_flow from dotenv import load_dotenv # Load environment variables @@ -46,101 +45,10 @@ """) def generate_content(topic): - llm = LLM( - model="command-r", - temperature=0.7 - ) - - search_tool = SerperDevTool(n_results=10) - - # First Agent: Senior Research Analyst - senior_research_analyst = Agent( - role="Senior Research Analyst", - goal=f"Research, analyze, and synthesize comprehensive information on {topic} from reliable web sources", - backstory="You're an expert research analyst with advanced web research skills. " - "You excel at finding, analyzing, and synthesizing information from " - "across the internet using search tools. You're skilled at " - "distinguishing reliable sources from unreliable ones, " - "fact-checking, cross-referencing information, and " - "identifying key patterns and insights. You provide " - "well-organized research briefs with proper citations " - "and source verification. Your analysis includes both " - "raw data and interpreted insights, making complex " - "information accessible and actionable.", - allow_delegation=False, - verbose=True, - tools=[search_tool], - llm=llm - ) - - # Second Agent: Content Writer - content_writer = Agent( - role="Content Writer", - goal="Transform research findings into engaging blog posts while maintaining accuracy", - backstory="You're a skilled content writer specialized in creating " - "engaging, accessible content from technical research. " - "You work closely with the Senior Research Analyst and excel at maintaining the perfect " - "balance between informative and entertaining writing, " - "while ensuring all facts and citations from the research " - "are properly incorporated. You have a talent for making " - "complex topics approachable without oversimplifying them.", - allow_delegation=False, - verbose=True, - llm=llm - ) - - # Research Task - research_task = Task( - description=(""" - 1. Conduct comprehensive research on {topic} including: - - Recent developments and news - - Key industry trends and innovations - - Expert opinions and analyses - - Statistical data and market insights - 2. Evaluate source credibility and fact-check all information - 3. Organize findings into a structured research brief - 4. Include all relevant citations and sources - """), - expected_output="""A detailed research report containing: - - Executive summary of key findings - - Comprehensive analysis of current trends and developments - - List of verified facts and statistics - - All citations and links to original sources - - Clear categorization of main themes and patterns - Please format with clear sections and bullet points for easy reference.""", - agent=senior_research_analyst - ) - - # Writing Task - writing_task = Task( - description=(""" - Using the research brief provided, create an engaging blog post that: - 1. Transforms technical information into accessible content - 2. Maintains all factual accuracy and citations from the research - 3. Includes: - - Attention-grabbing introduction - - Well-structured body sections with clear headings - - Compelling conclusion - 4. Preserves all source citations in [Source: URL] format - 5. Includes a References section at the end - """), - expected_output="""A polished blog post in markdown format that: - - Engages readers while maintaining accuracy - - Contains properly structured sections - - Includes Inline citations hyperlinked to the original source url - - Presents information in an accessible yet informative way - - Follows proper markdown formatting, use H1 for the title and H3 for the sub-sections""", - agent=content_writer - ) - - # Create Crew - crew = Crew( - agents=[senior_research_analyst, content_writer], - tasks=[research_task, writing_task], - verbose=True - ) - - return crew.kickoff(inputs={"topic": topic}) + """ + Generate content using the new CrewAI Flow-based approach + """ + return generate_content_with_flow(topic) # Main content area if generate_button: diff --git a/ai_news_generator/main.py b/ai_news_generator/main.py new file mode 100644 index 000000000..49af79ac7 --- /dev/null +++ b/ai_news_generator/main.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +""" +Main entry point for the AI News Generator using CrewAI Flows. + +This script provides a command-line interface to generate news content +using the NewsGeneratorFlow implementation. +""" + +import argparse +import sys +from typing import Optional +from news_flow import NewsGeneratorFlow + + +def main(): + """ + Main function to run the AI News Generator Flow from command line. + """ + parser = argparse.ArgumentParser( + description="Generate AI news content using CrewAI Flows", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python main.py --topic "Latest developments in artificial intelligence" + python main.py --topic "Climate change solutions" --output article.md + """ + ) + + parser.add_argument( + "--topic", + type=str, + required=True, + help="Topic to research and write about" + ) + + parser.add_argument( + "--output", + type=str, + help="Output file to save the generated content (optional)" + ) + + parser.add_argument( + "--verbose", + action="store_true", + help="Enable verbose logging" + ) + + args = parser.parse_args() + + if not args.topic.strip(): + print("Error: Topic cannot be empty", file=sys.stderr) + sys.exit(1) + + try: + print(f"πŸš€ Starting AI News Generator Flow for topic: '{args.topic}'") + print("πŸ“Š Initializing research phase...") + + # Create and run the flow + flow = NewsGeneratorFlow() + result = flow.kickoff(inputs={"topic": args.topic}) + + # Convert result to string if it's not already + content = str(result) + + print("βœ… Content generation completed!") + + # Save to file if specified + if args.output: + try: + with open(args.output, 'w', encoding='utf-8') as f: + f.write(content) + print(f"πŸ’Ύ Content saved to: {args.output}") + except IOError as e: + print(f"Error saving file: {e}", file=sys.stderr) + sys.exit(1) + else: + # Print to stdout + print("\n" + "="*80) + print("GENERATED CONTENT:") + print("="*80) + print(content) + print("="*80) + + return 0 + + except KeyboardInterrupt: + print("\n⚠️ Operation cancelled by user", file=sys.stderr) + return 1 + except Exception as e: + print(f"❌ Error: {e}", file=sys.stderr) + if args.verbose: + import traceback + traceback.print_exc() + return 1 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/ai_news_generator/news_flow.py b/ai_news_generator/news_flow.py new file mode 100644 index 000000000..49cbb2335 --- /dev/null +++ b/ai_news_generator/news_flow.py @@ -0,0 +1,193 @@ +import os +from typing import Any, Dict +from pydantic import BaseModel +from crewai import Agent, Task, Crew, LLM +from crewai.flow.flow import Flow, listen, start +from crewai_tools import SerperDevTool +from dotenv import load_dotenv + +load_dotenv() + + +class ResearchState(BaseModel): + """State model for research findings""" + topic: str + research_brief: str + sources: list[str] = [] + key_findings: list[str] = [] + + +class NewsGeneratorFlow(Flow[ResearchState]): + """ + CrewAI Flow for generating AI news content. + + This flow orchestrates a two-phase process: + 1. Research Phase: Comprehensive research on the given topic + 2. Writing Phase: Transform research into engaging blog content + """ + + def __init__(self): + super().__init__() + self.llm = LLM( + model="command-r", + temperature=0.7 + ) + self.search_tool = SerperDevTool(n_results=10) + + def _create_research_agent(self) -> Agent: + """Create the Senior Research Analyst agent""" + return Agent( + role="Senior Research Analyst", + goal="Research, analyze, and synthesize comprehensive information from reliable web sources", + backstory=( + "You're an expert research analyst with advanced web research skills. " + "You excel at finding, analyzing, and synthesizing information from " + "across the internet using search tools. You're skilled at " + "distinguishing reliable sources from unreliable ones, " + "fact-checking, cross-referencing information, and " + "identifying key patterns and insights. You provide " + "well-organized research briefs with proper citations " + "and source verification. Your analysis includes both " + "raw data and interpreted insights, making complex " + "information accessible and actionable." + ), + allow_delegation=False, + verbose=True, + tools=[self.search_tool], + llm=self.llm + ) + + def _create_content_writer_agent(self) -> Agent: + """Create the Content Writer agent""" + return Agent( + role="Content Writer", + goal="Transform research findings into engaging blog posts while maintaining accuracy", + backstory=( + "You're a skilled content writer specialized in creating " + "engaging, accessible content from technical research. " + "You work closely with the Senior Research Analyst and excel at maintaining the perfect " + "balance between informative and entertaining writing, " + "while ensuring all facts and citations from the research " + "are properly incorporated. You have a talent for making " + "complex topics approachable without oversimplifying them." + ), + allow_delegation=False, + verbose=True, + llm=self.llm + ) + + @start() + def research_phase(self) -> str: + """ + Initial phase: Conduct comprehensive research on the topic + """ + research_agent = self._create_research_agent() + + research_task = Task( + description=f""" + 1. Conduct comprehensive research on {self.state.topic} including: + - Recent developments and news + - Key industry trends and innovations + - Expert opinions and analyses + - Statistical data and market insights + 2. Evaluate source credibility and fact-check all information + 3. Organize findings into a structured research brief + 4. Include all relevant citations and sources + """, + expected_output="""A detailed research report containing: + - Executive summary of key findings + - Comprehensive analysis of current trends and developments + - List of verified facts and statistics + - All citations and links to original sources + - Clear categorization of main themes and patterns + Please format with clear sections and bullet points for easy reference.""", + agent=research_agent + ) + + # Create a single-agent crew for research + research_crew = Crew( + agents=[research_agent], + tasks=[research_task], + verbose=True + ) + + # Execute research and return results + research_result = research_crew.kickoff(inputs={"topic": self.state.topic}) + + # Update state with research findings + self.state.research_brief = str(research_result) + + return str(research_result) + + @listen(research_phase) + def writing_phase(self, research_results: str) -> str: + """ + Second phase: Transform research into engaging blog content + """ + content_writer = self._create_content_writer_agent() + + writing_task = Task( + description=f""" + Using the research brief provided: {research_results} + + Create an engaging blog post that: + 1. Transforms technical information into accessible content + 2. Maintains all factual accuracy and citations from the research + 3. Includes: + - Attention-grabbing introduction + - Well-structured body sections with clear headings + - Compelling conclusion + 4. Preserves all source citations in [Source: URL] format + 5. Includes a References section at the end + """, + expected_output="""A polished blog post in markdown format that: + - Engages readers while maintaining accuracy + - Contains properly structured sections + - Includes Inline citations hyperlinked to the original source url + - Presents information in an accessible yet informative way + - Follows proper markdown formatting, use H1 for the title and H3 for the sub-sections""", + agent=content_writer + ) + + # Create a single-agent crew for writing + writing_crew = Crew( + agents=[content_writer], + tasks=[writing_task], + verbose=True + ) + + # Execute writing phase + writing_result = writing_crew.kickoff(inputs={ + "topic": self.state.topic, + "research_results": research_results + }) + + return str(writing_result) + + def kickoff(self, inputs: Dict[str, Any]) -> Any: + """ + Initialize and run the flow with the given topic + """ + # Initialize state with the topic + if not hasattr(self, 'state') or self.state is None: + self.state = ResearchState(topic=inputs.get("topic", "")) + else: + self.state.topic = inputs.get("topic", "") + + # Start the flow execution + return super().kickoff(inputs=inputs) + + +def generate_content_with_flow(topic: str) -> str: + """ + Convenience function to generate content using the NewsGeneratorFlow + + Args: + topic (str): The topic to research and write about + + Returns: + str: The generated blog post content + """ + flow = NewsGeneratorFlow() + result = flow.kickoff(inputs={"topic": topic}) + return str(result) \ No newline at end of file