deepakpant commited on
Commit
c34a4b0
·
1 Parent(s): cba9a3b

Added gradio UI

Browse files
.env.example CHANGED
@@ -1,2 +1,6 @@
1
  MODEL=gemini/gemini-1.5-flash
2
- GEMINI_API_KEY=<gemini_api_key> # Your API key here
 
 
 
 
 
1
  MODEL=gemini/gemini-1.5-flash
2
+ GEMINI_API_KEY=<gemini_api_key> # Your API key here
3
+ NEO_DB_URI = <neo4j_uri> # Your URI here
4
+ NEO_DB_USERNAME = <neo4j_username> # Your username here
5
+ NEO_DB_PWD = <neo4j_password> # Your password here
6
+ NEO_DB_DATABSE = <neo4j_database> # Your database here
knowledge/cities.json ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cities": [
3
+ "Seattle",
4
+ "Rome",
5
+ "Manchester",
6
+ "Schimmert",
7
+ "Buenos Aires",
8
+ "Chicago",
9
+ "New York City",
10
+ "Redwood City",
11
+ "Sydney",
12
+ "Auckland",
13
+ "Brisbane",
14
+ "Neuilly",
15
+ "Munich",
16
+ "Milan",
17
+ "Paris",
18
+ "Shanghai",
19
+ "Longmont",
20
+ "Houston",
21
+ "Tampa",
22
+ "Chattanooga",
23
+ "Philadelphia",
24
+ "Visalia",
25
+ "Dinard",
26
+ "London",
27
+ "Fremont",
28
+ "Pleasanton",
29
+ "Bangalore",
30
+ "Toronto",
31
+ "Lausanne",
32
+ "Paceco",
33
+ "Horgen",
34
+ "Makhachkala",
35
+ "Mozzate",
36
+ "Jaux",
37
+ "Porto",
38
+ "Kuala Lumpur",
39
+ "Torredembarra",
40
+ "Madrid",
41
+ "Columbia",
42
+ "San Francisco",
43
+ "Copenhagen",
44
+ "Perth",
45
+ "West Perth",
46
+ "Weehawken",
47
+ "Montreal",
48
+ "Quebec City",
49
+ "Saint-Priest-de-Gimel",
50
+ "Saint-Priest",
51
+ "Singapore",
52
+ "Jersey City",
53
+ "Würzburg",
54
+ "Düsseldorf",
55
+ "Urbandale",
56
+ "Naples",
57
+ "Hilversum",
58
+ "São Paulo",
59
+ "St. Albert",
60
+ "San Diego",
61
+ "Ronkonkoma",
62
+ "Chatswood",
63
+ "Conshohocken",
64
+ "Overland Park",
65
+ "Victoria",
66
+ "Meridian",
67
+ "Irvine",
68
+ "Atlanta",
69
+ "Dallas",
70
+ "Paradise Valley",
71
+ "Prescott Valley",
72
+ "Plano",
73
+ "Wayne",
74
+ "Ottawa",
75
+ "Gilbert",
76
+ "Milwaukee",
77
+ "Los Angeles",
78
+ "Boston",
79
+ "Wellington",
80
+ "Eden Prairie",
81
+ "Groningen",
82
+ "Amsterdam",
83
+ "Kyiv",
84
+ "Dnipro",
85
+ "Ahmedabad",
86
+ "Hollywood",
87
+ "Portland",
88
+ "Carlsbad",
89
+ "Columbus",
90
+ "Bentonville",
91
+ "Charlotte",
92
+ "The Rocks",
93
+ "Chesterfield",
94
+ "Rio de Janeiro",
95
+ "Créteil",
96
+ "Oakland",
97
+ "Phoenix",
98
+ "Indianapolis",
99
+ "Best",
100
+ "Raleigh",
101
+ "Pune",
102
+ "Durham"
103
+ ]
104
+ }
knowledge/content_style_mapping.json DELETED
@@ -1,48 +0,0 @@
1
- {
2
- "target_audience": {
3
- "linkedin_post": {
4
- "format": "post",
5
- "tone": "professional"
6
- },
7
- "whatsapp_message": {
8
- "format": "chat",
9
- "tone": "casual"
10
- },
11
- "tweet": {
12
- "format": "tweet",
13
- "tone": "straightforward"
14
- },
15
- "news_article": {
16
- "format": "article",
17
- "tone": "neutral"
18
- },
19
- "technical_blog": {
20
- "format": "blog",
21
- "tone": "professional"
22
- },
23
- "formal_email": {
24
- "format": "email",
25
- "tone": "professional"
26
- },
27
- "instagram_post": {
28
- "format": "post",
29
- "tone": "friendly"
30
- },
31
- "website_content": {
32
- "format": "report",
33
- "tone": "neutral"
34
- },
35
- "marketing_email": {
36
- "format": "email",
37
- "tone": "confident"
38
- },
39
- "job_application": {
40
- "format": "email",
41
- "tone": "professional"
42
- },
43
- "customer_support_response": {
44
- "format": "chat",
45
- "tone": "friendly"
46
- }
47
- }
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
knowledge/format.json DELETED
@@ -1,46 +0,0 @@
1
- {
2
- "format": {
3
- "post": {
4
- "name": "Post",
5
- "description": "A short and engaging content piece, often used for platforms like LinkedIn or Facebook. It highlights key points and encourages interaction, such as likes, shares, or comments.",
6
- "max_length": 300,
7
- "negative": "Avoid long paragraphs, excessive technical jargon, or overly formal language. Do not make the content too detailed or complex."
8
- },
9
- "chat": {
10
- "name": "Chat",
11
- "description": "A conversational and interactive response that mimics real-time messaging. It focuses on direct, clear, and natural communication with a friendly tone.",
12
- "max_length": 200,
13
- "negative": "Do not use overly formal language, complex sentence structures, or impersonal tones. Avoid too much technical information and hashtags."
14
- },
15
- "tweet": {
16
- "name": "Tweet",
17
- "description": "A concise and impactful message designed for Twitter or similar platforms. It often uses hashtags, mentions, or trending topics to increase visibility.",
18
- "max_length": 280,
19
- "negative": "Avoid lengthy explanations, irrelevant details, or complex sentences. Do not exceed the character limit or use a formal tone."
20
- },
21
- "email": {
22
- "name": "Email",
23
- "description": "A formal or semi-formal written message for direct communication. It includes a clear subject line, opening, body, and closing, often addressing specific recipients.",
24
- "max_length": 1500,
25
- "negative": "Avoid casual or overly brief content. Do not make the email too long or include excessive details that deviate from the purpose."
26
- },
27
- "blog": {
28
- "name": "Blog",
29
- "description": "A detailed and informative piece written for online readers. It includes a compelling introduction, multiple sections with headings, and a conclusion to engage and educate the audience.",
30
- "max_length": 2000,
31
- "negative": "Avoid overly technical language, jargon, or too formal language. Do not make the blog too short or lacking in detail."
32
- },
33
- "article": {
34
- "name": "Article",
35
- "description": "A comprehensive, well-researched write-up, often featuring in-depth analysis or expert perspectives. Suitable for magazines, websites, or journals to inform or persuade a broad audience.",
36
- "max_length": 3000,
37
- "negative": "Do not oversimplify complex topics, avoid using unverified sources or vague statements. Avoid making the article too conversational or informal."
38
- },
39
- "report": {
40
- "name": "Report",
41
- "description": "A structured document that presents facts, data, and analysis for a specific audience or purpose. It often includes charts, tables, and a clear summary of findings.",
42
- "max_length": 5000,
43
- "negative": "Avoid informal language, speculative content, or a lack of data. Do not make the report subjective or unorganized."
44
- }
45
- }
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
knowledge/industries.json ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "industries": [
3
+ "Technology",
4
+ "Healthcare",
5
+ "Finance",
6
+ "Education",
7
+ "Retail",
8
+ "Manufacturing",
9
+ "Transportation",
10
+ "Entertainment",
11
+ "Real Estate",
12
+ "Energy",
13
+ "Telecommunications",
14
+ "Hospitality",
15
+ "Food & Beverage",
16
+ "Automotive",
17
+ "Aerospace",
18
+ "Biotechnology",
19
+ "Pharmaceuticals",
20
+ "Construction",
21
+ "Consulting",
22
+ "Media",
23
+ "Agriculture",
24
+ "Legal",
25
+ "Insurance",
26
+ "E-commerce",
27
+ "Cybersecurity",
28
+ "Sports",
29
+ "Logistics",
30
+ "Government",
31
+ "Nonprofit",
32
+ "Mining",
33
+ "Waste Management",
34
+ "Textile",
35
+ "Shipping",
36
+ "Marketing",
37
+ "Tourism",
38
+ "Advertising",
39
+ "Electronics",
40
+ "Defense",
41
+ "Architecture",
42
+ "Music",
43
+ "Publishing",
44
+ "Gaming",
45
+ "Social Media",
46
+ "Investment",
47
+ "Artificial Intelligence",
48
+ "Blockchain",
49
+ "Cloud Computing",
50
+ "Robotics",
51
+ "Nanotechnology",
52
+ "Environmental Services"
53
+ ]
54
+ }
knowledge/target_audience.json DELETED
@@ -1,59 +0,0 @@
1
- {
2
- "target_audience": {
3
- "linkedin_post": {
4
- "name": "LinkedIn Post",
5
- "description": "Aimed at professionals, thought leaders, and businesses. Content is typically career-focused or industry-related, designed to spark engagement among people seeking professional growth, networking, or knowledge.",
6
- "ideal_audience": "Business professionals, marketers, entrepreneurs, recruiters, job seekers, industry experts"
7
- },
8
- "whatsapp_message": {
9
- "name": "WhatsApp Message",
10
- "description": "Casual or semi-formal communication meant for direct, personal messaging. Used for sending quick updates, reminders, or messages to small groups, often informal in tone.",
11
- "ideal_audience": "Friends, family, small teams, personal contacts, colleagues in a more relaxed setting"
12
- },
13
- "tweet": {
14
- "name": "Tweet",
15
- "description": "Aimed at a broad, diverse audience on Twitter, from the general public to niche communities. Tweets are brief and designed to provoke quick engagement, whether through likes, retweets, or comments.",
16
- "ideal_audience": "General public, influencers, content creators, tech enthusiasts, trend followers, entertainment fans"
17
- },
18
- "news_article": {
19
- "name": "News Article",
20
- "description": "Targeted at a wide audience seeking timely, relevant information. News articles are often written for readers who are looking for the latest updates, in-depth analysis, or expert opinions on current events or trending topics.",
21
- "ideal_audience": "General public, journalists, news enthusiasts, academics, policy makers, professionals interested in current affairs"
22
- },
23
- "technical_blog": {
24
- "name": "Technical Blog",
25
- "description": "Aimed at professionals, experts, or hobbyists in specialized fields like technology, software development, engineering, or data science. The content is typically in-depth, offering insights, tutorials, and technical knowledge.",
26
- "ideal_audience": "Software developers, engineers, data scientists, tech entrepreneurs, IT professionals, learners seeking advanced technical skills"
27
- },
28
- "formal_email": {
29
- "name": "Formal Email",
30
- "description": "Designed for business or professional communication. The email is meant for communicating with colleagues, clients, business partners, or supervisors in a polite, respectful, and formal tone.",
31
- "ideal_audience": "Business professionals, corporate partners, clients, managers, senior leadership, job candidates"
32
- },
33
- "instagram_post": {
34
- "name": "Instagram Post",
35
- "description": "Aimed at a visually-driven, creative audience. Instagram posts are often used for branding, lifestyle content, promotions, or personal expression, with a focus on imagery and concise captions.",
36
- "ideal_audience": "Millennials, Gen Z, influencers, lifestyle bloggers, fashion enthusiasts, foodies, beauty influencers, brand followers"
37
- },
38
- "website_content": {
39
- "name": "Website Content",
40
- "description": "Targeted at a broad range of users, from potential customers to casual visitors. Website content needs to be informative, engaging, and easily navigable, with a focus on conversion and user experience.",
41
- "ideal_audience": "Consumers, visitors, potential customers, search engine users, anyone looking for information or services"
42
- },
43
- "marketing_email": {
44
- "name": "Marketing Email",
45
- "description": "Aimed at potential customers or existing leads. Marketing emails are designed to drive action, whether it’s to purchase a product, sign up for a service, or engage with a special offer.",
46
- "ideal_audience": "Prospective customers, leads, subscribers, clients, consumers interested in sales promotions"
47
- },
48
- "job_application": {
49
- "name": "Job Application",
50
- "description": "Directed towards hiring managers, recruiters, or HR professionals. The job application content should clearly convey qualifications, skills, and the applicant's interest in a specific role.",
51
- "ideal_audience": "Recruiters, hiring managers, HR professionals, employers, staffing agencies"
52
- },
53
- "customer_support_response": {
54
- "name": "Customer Support Response",
55
- "description": "Meant for existing or potential customers seeking assistance with a product or service. The tone should be empathetic, understanding, and solution-oriented, addressing the customer’s issue in a clear, helpful manner.",
56
- "ideal_audience": "Customers with inquiries, complaints, technical issues, or service requests"
57
- }
58
- }
59
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
knowledge/tone.json DELETED
@@ -1,36 +0,0 @@
1
- {
2
- "tone": {
3
- "professional": {
4
- "name": "Professional",
5
- "description": "Formal and respectful, suited for workplace communication."
6
- },
7
- "casual": {
8
- "name": "Casual",
9
- "description": "Relaxed and conversational, suitable for informal interactions."
10
- },
11
- "straightforward": {
12
- "name": "Straightforward",
13
- "description": "Direct and concise, avoiding unnecessary details."
14
- },
15
- "confident": {
16
- "name": "Confident",
17
- "description": "Assertive and positive, showing conviction in the message."
18
- },
19
- "friendly": {
20
- "name": "Friendly",
21
- "description": "Warm and approachable, making the user feel comfortable."
22
- },
23
- "neutral": {
24
- "name": "Neutral",
25
- "description": "Objective and balanced, without bias or emotion."
26
- },
27
- "storytelling": {
28
- "name": "Storytelling",
29
- "description": "Narrative style, weaving details into a compelling story."
30
- },
31
- "inspirational": {
32
- "name": "Inspirational",
33
- "description": "Uplifting and encouraging, motivating the user."
34
- }
35
- }
36
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pyproject.toml CHANGED
@@ -6,12 +6,14 @@ authors = [{ name = "Deepak Pant", email = "deepak.93p@gmail.com" }]
6
  requires-python = ">=3.10,<=3.13"
7
  dependencies = [
8
  "crewai[tools]>=0.86.0,<1.0.0",
9
- "gradio>=5.12.0,<5.13.0"
 
10
  ]
11
 
12
  [project.scripts]
13
  report_genie = "report_genie.app:launch"
14
  run_crew = "report_genie.main:run"
 
15
  train = "report_genie.main:train"
16
  replay = "report_genie.main:replay"
17
  test = "report_genie.main:test"
 
6
  requires-python = ">=3.10,<=3.13"
7
  dependencies = [
8
  "crewai[tools]>=0.86.0,<1.0.0",
9
+ "gradio>=5.12.0,<5.13.0",
10
+ "neo4j>=5.28.1,<6.0.0",
11
  ]
12
 
13
  [project.scripts]
14
  report_genie = "report_genie.app:launch"
15
  run_crew = "report_genie.main:run"
16
+ # run_crew = "report_genie.app:launch"
17
  train = "report_genie.main:train"
18
  replay = "report_genie.main:replay"
19
  test = "report_genie.main:test"
src/report_genie/app.py CHANGED
@@ -1,131 +1,57 @@
1
  import gradio as gr
2
  import os
3
- from report_genie.crew import ExpresslyServer
 
4
 
5
 
6
- def call(prompt, target_audience, format, tone, active_tab):
7
- """
8
- Calls the Expressly Server API to generate content based on the given inputs.
9
-
10
- Args:
11
- prompt (str): The text prompt for the chat.
12
- target_audience (str): The target audience for the response.
13
- format (str): The format of the response, e.g., text, markdown.
14
- tone (str): The tone of the response, e.g., formal, informal.
15
- active_tab (str): The active tab on the UI, either "target_audience" or "format_tone".
16
-
17
- Returns:
18
- str: The generated text response.
19
-
20
- Raises:
21
- ValueError: If prompt is empty, or if the active_tab value is invalid.
22
- """
23
-
24
- # Validating and constructing the inputs
25
- if prompt is None or prompt == "":
26
- raise ValueError("Prompt is required")
27
 
28
- if active_tab == "target_audience":
29
- format = ""
30
- tone = ""
31
- elif active_tab == "format_tone":
32
- target_audience = ""
33
- else:
34
- raise ValueError("Invalid active_tab value")
35
 
 
 
36
  inputs = {
37
- "prompt": prompt,
38
- "target_audience": target_audience,
39
- "format": format,
40
- "tone": tone,
41
  }
42
 
43
- outputs = ExpresslyServer().crew().kickoff(inputs=inputs)
 
 
 
44
 
45
- if outputs is None:
46
  result = "Please check the inputs and try again. If the issue persists, contact support."
47
  else:
48
- result = outputs.raw
49
 
50
  return result
51
 
52
 
 
53
  with gr.Blocks() as app:
54
- gr.Markdown("# Expressly - Text Transformation App")
55
-
 
56
  with gr.Row():
57
- # Left Column for Inputs
58
- with gr.Column(scale=1):
59
- prompt = gr.Textbox(label="Message Expressly", max_length=1024, lines=3)
60
-
61
- # Create a state variable to store active tab
62
- active_tab = gr.State("target_audience")
63
-
64
- with gr.Tab("Target Audience", id="tab_audience") as tab1:
65
- target_audience = gr.Dropdown(
66
- [
67
- "LinkedIn Post",
68
- "WhatsApp Message",
69
- "Tweet",
70
- "News Article",
71
- "Technical Blog",
72
- "Formal Email",
73
- "Instagram Post",
74
- "Website Content",
75
- "Marketing Email",
76
- "Job Application",
77
- "Customer Support Response",
78
- ],
79
- label="Target Audience",
80
- info="Pick a Target Audience to specify the purpose or platform.",
81
- )
82
- # Update state when this tab is selected
83
- tab1.select(lambda: "target_audience", None, active_tab)
84
-
85
- with gr.Tab("Format & Tone", id="tab_format") as tab2:
86
- format = gr.Dropdown(
87
- [
88
- "Post",
89
- "Chat",
90
- "Tweet",
91
- "Email",
92
- "Blog",
93
- "Article",
94
- "Report",
95
- "Product Description",
96
- ],
97
- label="Format",
98
- info="Choose a Format to define the type of content.",
99
- )
100
- tone = gr.Dropdown(
101
- [
102
- "Professional",
103
- "Casual",
104
- "Straightforward",
105
- "Confident",
106
- "Friendly",
107
- "Neutral",
108
- "Storytelling",
109
- "Inspirational",
110
- ],
111
- label="Tone",
112
- info="Select a Tone to set the communication style.",
113
- )
114
- # Update state when this tab is selected
115
- tab2.select(lambda: "format_tone", None, active_tab)
116
-
117
- btn_submit = gr.Button("Submit")
118
-
119
- # Right Column for Output
120
- with gr.Column(scale=1):
121
- results = gr.Markdown(label="Result")
122
-
123
- btn_submit.click(
124
- fn=call,
125
- inputs=[prompt, target_audience, format, tone, active_tab],
126
- outputs=[results],
127
- )
128
-
129
 
130
  def launch():
131
  """
 
1
  import gradio as gr
2
  import os
3
+ from report_genie.crew import ReportGenieServer
4
+ from report_genie.utils.utils import load_json_data
5
 
6
 
7
+ # Load the JSON files
8
+ CITIES_JSON_FILE = "cities.json"
9
+ INDUSTRIES_JSON_FILE = "industries.json"
10
+ KNOWLEDGE_SOURCE_PATH = "knowledge"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ cities = load_json_data(CITIES_JSON_FILE, KNOWLEDGE_SOURCE_PATH)
13
+ industries = load_json_data(INDUSTRIES_JSON_FILE, KNOWLEDGE_SOURCE_PATH)
 
 
 
 
 
14
 
15
+ def generate_report(city, industry):
16
+
17
  inputs = {
18
+ "city_name": city,
19
+ "industry_name": industry,
 
 
20
  }
21
 
22
+ try:
23
+ output = ReportGenieServer().crew().kickoff(inputs=inputs)
24
+ except Exception:
25
+ output = None
26
 
27
+ if output is None:
28
  result = "Please check the inputs and try again. If the issue persists, contact support."
29
  else:
30
+ result = output.raw
31
 
32
  return result
33
 
34
 
35
+ ## Gradio UI
36
  with gr.Blocks() as app:
37
+ gr.Markdown("# 📄 Report Genie - An AI-powered automatic report generator")
38
+ gr.Markdown("Report Genie is an AI-driven report generation tool that automates the process of creating detailed and structured reports. Leveraging the power of **CrewAI** for task delegation and **Gradio** for an interactive user interface, this application streamlines report generation with minimal user input.")
39
+
40
  with gr.Row():
41
+ city_input = gr.Dropdown(choices=cities.get("cities"), label="Select City")
42
+ industry_input = gr.Dropdown(choices=industries.get("industries"), label="Select Industry")
43
+ submit_button = gr.Button("Generate Report")
44
+
45
+ gr.Markdown("---")
46
+ gr.Markdown("---")
47
+ gr.Markdown("---")
48
+
49
+ report_output = gr.Markdown(value="", label="Report", visible=False)
50
+
51
+ def on_submit(city, industry):
52
+ return gr.update(value=generate_report(city, industry), visible=True)
53
+
54
+ submit_button.click(on_submit, inputs=[city_input, industry_input], outputs=report_output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  def launch():
57
  """
src/report_genie/config/agents.yaml CHANGED
@@ -1,10 +1,30 @@
1
- content_creator:
2
  role: >
3
- Senior Content Creator
4
  goal: >
5
- Created engaging and informative content
6
  backstory: >
7
- You're a seasoned content creator with a knack for producing high-quality
8
- content that captivates and educates your audience. You're known for your
9
- ability to translate complex concepts into clear and engaging narratives,
10
- making it easy for others to learn and understand.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ data_researcher:
2
  role: >
3
+ Data Researcher
4
  goal: >
5
+ Gather comprehensive information about specific companies that are in relevant cities and industries
6
  backstory: >
7
+ You are an expert data researcher with deep knowledge of
8
+ business ecosystems and city demographics. You excel at analyzing
9
+ complex data relationships.
10
+
11
+ news_analyst:
12
+ role: >
13
+ News Analyst
14
+ goal: >
15
+ Find and analyze recent news about relevant companies in the specified industry and city
16
+ backstory: >
17
+ You are a seasoned news analyst with expertise in
18
+ business journalism and market research. You can identify key trends
19
+ and developments from news articles.
20
+
21
+ report_writer:
22
+ role: >
23
+ Report Writer
24
+ goal: >
25
+ Create comprehensive, well-structured reports combining the provided research and news analysis. Do not include any information that isnt explicitly provided.
26
+ backstory: >
27
+ You are a professional report writer with experience in
28
+ business intelligence and market analysis. You excel at synthesizing
29
+ information into clear, actionable insights. Do not include any information that isn't explicitly provided.
30
+
src/report_genie/config/tasks.yaml CHANGED
@@ -1,40 +1,25 @@
1
- content_creator_task:
2
  description: >
3
- Understand the context carefully
4
-
5
- ---
6
- Context:
7
- ({context})
8
- ---
9
-
10
- Describe the context in a way that would follow the tone, format, and guidelines provided.
11
-
12
- ---
13
- Tone:(
14
- [Tone: {tone[name]}] ###
15
- [Tone Description: {tone[description]}] ###
16
- )
17
- ---
18
-
19
- ---
20
- Format:(
21
- [Format Type: {format[name]}] ###
22
- [Format Description:{format[description]}] ###
23
- [Max Content Length: {format[max_length]}] ###
24
- [Negetive: {format[negative]}])
25
- )
26
- ---
27
 
28
- ---
29
- Target Audience: (
30
- [Target Audience: {target_audience[name]}] ###
31
- [Target Audience Description: {target_audience[description]}] ###
32
- [Ideal Audience Description: {target_audience[ideal_audience]}] ###
33
- )
34
- ---
35
 
36
- Make sure you find the latest information about the topic in the internet if needed and provide a well-researched content.
 
 
 
 
37
  expected_output: >
38
- A fully fledge output with the desised format & tone and the the Guidelines provided in the description.
39
- Formatted as markdown without '```'
40
- agent: content_creator
 
1
+ city_research_task:
2
  description: >
3
+ Research and analyze {city_name} and its business ecosystem in {industry_name} industry:
4
+ 1. Get city summary and key information
5
+ 2. Find organizations in the specified industry
6
+ 3. Analyze business relationships and economic indicators
7
+ expected_output: >
8
+ Basic statistics about the companies in the given city and industry as well as top performers
9
+ agent: data_researcher
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ news_analysis_task:
12
+ description: >
13
+ Analyze recent news about the companies provided by the city researcher
14
+ expected_output: >
15
+ Summarization of the latest news for the company and how it might affect the market
16
+ agent: news_analyst
 
17
 
18
+ report_writing_task:
19
+ description: >
20
+ Create a detailed markdown report about the
21
+ results you got from city research and news analysis tasks.
22
+ Do not include any information that isn't provided
23
  expected_output: >
24
+ Markdown summary report with key insights and recommendations.
25
+ agent: report_writer
 
src/report_genie/crew.py CHANGED
@@ -7,6 +7,7 @@ import os
7
  from typing import Dict, Any, Optional
8
  import json
9
  from report_genie.utils.utils import load_json_data, sanitize_input
 
10
 
11
  load_dotenv()
12
 
@@ -21,134 +22,92 @@ KNOWLEDGE_SOURCE_PATH = "knowledge"
21
 
22
 
23
  @CrewBase
24
- class ExpresslyServer:
25
  """ExpresslyServer crew"""
26
 
27
- # Create a knowledge source
28
- json_knowledge_source = JSONKnowledgeSource(
29
- file_paths=["format.json", "tone.json", "target_audience.json"],
30
- )
31
-
32
  agents_config = "config/agents.yaml"
33
  tasks_config = "config/tasks.yaml"
34
 
35
  llm = LLM(model=MODEL, api_key=GEMINI_API_KEY, temperature=0.7)
36
 
37
- @before_kickoff
38
- def validate_inputs(
39
- self, inputs: Optional[Dict[str, Any]]
40
- ) -> Optional[Dict[str, Any]]:
41
  """
42
- Validate and process user inputs based on the active tab selection.
43
-
44
- This method checks the integrity and presence of required inputs, loads
45
- necessary JSON data, and validates the active_tab value to ensure the
46
- appropriate fields are populated. It formats the inputs for further processing.
47
-
48
- Parameters:
49
- inputs (Optional[Dict[str, Any]]): The dictionary containing user inputs,
50
- including 'target_audience', 'format', 'tone', 'active_tab', and 'prompt'.
51
-
52
- Returns:
53
- Optional[Dict[str, Any]]: A dictionary formatted with context, format, tone,
54
- and target_audience details based on the inputs provided.
55
-
56
- Raises:
57
- ValueError: If inputs are missing, not a dictionary, or required fields
58
- ('active_tab', 'prompt', 'format', 'tone', 'target_audience') are not provided
59
- or invalid.
60
  """
61
-
62
- if inputs is None or len(inputs) == 0 or not isinstance(inputs, dict):
63
- raise ValueError("Inputs is required and must be a dictionary")
64
-
65
- ## Get the first element from the list of inputs and get the value of target, format, active_tab and prompt
66
- query = inputs
67
- target_audience: str = sanitize_input(query.get("target_audience"))
68
- content_format: str = sanitize_input(query.get("format"))
69
- content_tone: str = sanitize_input(query.get("tone"))
70
- prompt: str = query.get("prompt")
71
-
72
- # Check if prompt are not None
73
- if prompt is None:
74
- raise ValueError("Prompt is required")
75
-
76
- # Load JSON data from content_style_mapping.json
77
- content_style_mapping_json = load_json_data(
78
- CONTENT_STYLE_MAPPING_JSON_FILE, KNOWLEDGE_SOURCE_PATH
79
- )
80
- tone_json = load_json_data(TONE_JSON_FILE, KNOWLEDGE_SOURCE_PATH)
81
- format_json = load_json_data(FORMAT_JSON_FILE, KNOWLEDGE_SOURCE_PATH)
82
- target_audience_json = load_json_data(
83
- TARGET_AUDIENCE_JSON_FILE, KNOWLEDGE_SOURCE_PATH
84
  )
85
 
86
- if target_audience != "":
87
- ## Resetting the format and tone as per the target audience
88
- mappings: dict = content_style_mapping_json.get("target_audience").get(
89
- target_audience
90
- )
91
- format_dict = format_json.get("format").get(mappings.get("format"))
92
- tone_dict = tone_json.get("tone").get(mappings.get("tone"))
93
- target_audience_dict = target_audience_json.get("target_audience").get(
94
- target_audience
95
- )
96
- elif content_format != "" and content_tone != "":
97
- ## Constructing a target_audience_dict with empty values and populating the format and tone as per the input
98
- target_audience_dict = {
99
- "name": "",
100
- "description": "",
101
- "ideal_audience": "",
102
- }
103
- format_dict = format_json.get("format").get(content_format)
104
- tone_dict = tone_json.get("tone").get(content_tone)
105
- else:
106
- raise ValueError("Provide either target audience or format and tone")
107
-
108
- ## Format the inputs
109
- inputs = {
110
- "context": prompt,
111
- "format": format_dict,
112
- "tone": tone_dict,
113
- "target_audience": target_audience_dict,
114
- }
115
-
116
- return inputs
117
-
118
  @agent
119
- def content_creator(self) -> Agent:
120
  """
121
- Initializes and returns an Agent for content creation.
122
-
123
- This agent is configured using predefined settings for the content creator
124
- and utilizes a language model (LLM) for generating content. The agent
125
- accesses a JSON knowledge source to enhance its capabilities and operates
126
- in verbose mode for detailed output logging.
127
-
128
- Returns:
129
- Agent: An initialized agent configured for content creation.
130
  """
 
 
 
 
 
 
131
 
 
 
 
 
 
 
 
 
132
  return Agent(
133
- config=self.agents_config["content_creator"],
134
  llm=self.llm,
135
- knowledge_source=[self.json_knowledge_source],
136
  verbose=True,
137
  )
138
 
139
  @task
140
- def content_creator_task(self) -> Task:
 
 
 
 
 
141
  """
142
- Initializes and returns a Task for content creation.
 
 
143
 
144
- This task is configured using predefined settings for content creation
145
- and is used by the content creator agent to generate content.
 
 
 
 
 
 
 
 
 
 
146
 
147
- Returns:
148
- Task: An initialized task configured for content creation.
149
  """
 
 
 
 
 
150
  return Task(
151
- config=self.tasks_config["content_creator_task"],
 
152
  )
153
 
154
  @crew
@@ -159,6 +118,5 @@ class ExpresslyServer:
159
  agents=self.agents,
160
  tasks=self.tasks,
161
  process=Process.sequential,
162
- knowledge_source=[self.json_knowledge_source],
163
  verbose=True,
164
  )
 
7
  from typing import Dict, Any, Optional
8
  import json
9
  from report_genie.utils.utils import load_json_data, sanitize_input
10
+ from report_genie.tools.neo4j_tools import get_city_info, get_news
11
 
12
  load_dotenv()
13
 
 
22
 
23
 
24
  @CrewBase
25
+ class ReportGenieServer:
26
  """ExpresslyServer crew"""
27
 
 
 
 
 
 
28
  agents_config = "config/agents.yaml"
29
  tasks_config = "config/tasks.yaml"
30
 
31
  llm = LLM(model=MODEL, api_key=GEMINI_API_KEY, temperature=0.7)
32
 
33
+ @agent
34
+ def data_researcher(self) -> Agent:
 
 
35
  """
36
+ The data_researcher agent is responsible for fetching data from the given data sources.
37
+ It takes the data sources as input and returns the data as JSON.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  """
39
+ return Agent(
40
+ config=self.agents_config["data_researcher"],
41
+ llm=self.llm,
42
+ tools=[get_city_info],
43
+ verbose=True,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  )
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  @agent
47
+ def news_analyst(self) -> Agent:
48
  """
49
+ The news_analyst agent is responsible for analyzing news articles
50
+ related to the data fetched by the data_researcher agent.
51
+ It takes the data sources as input and returns a JSON object
52
+ containing the analysis results.
 
 
 
 
 
53
  """
54
+ return Agent(
55
+ config=self.agents_config["news_analyst"],
56
+ llm=self.llm,
57
+ tools=[get_news],
58
+ verbose=True,
59
+ )
60
 
61
+ @agent
62
+ def report_writer(self) -> Agent:
63
+ """
64
+ The report_writer agent is responsible for generating a report given the
65
+ results from the data_researcher and news_analyst agents.
66
+ It takes the results from the agents as input and returns a JSON object
67
+ containing the report.
68
+ """
69
  return Agent(
70
+ config=self.agents_config["report_writer"],
71
  llm=self.llm,
 
72
  verbose=True,
73
  )
74
 
75
  @task
76
+ def city_research_task(self) -> Task:
77
+ """
78
+ The city_research_task task is responsible for executing the data_researcher
79
+ and news_analyst agents in order.
80
+ It takes no input and returns a JSON object containing the results of the
81
+ agents.
82
  """
83
+ return Task(
84
+ config=self.tasks_config["city_research_task"],
85
+ )
86
 
87
+ @task
88
+ def news_analysis_task(self) -> Task:
89
+ """
90
+ The news_analysis_task task is responsible for executing the news_analyst
91
+ agent to analyze news articles related to the data fetched by the
92
+ data_researcher agent.
93
+ It takes no input and returns a JSON object containing the analysis results.
94
+ """
95
+ return Task(
96
+ config=self.tasks_config["news_analysis_task"],
97
+ context=[self.city_research_task()],
98
+ )
99
 
100
+ @task
101
+ def report_writing_task(self) -> Task:
102
  """
103
+ The report_writing_task task is responsible for executing the report_writer
104
+ agent to generate a report based on the findings from previous tasks.
105
+ It takes no input and returns a JSON object containing the generated report.
106
+ """
107
+
108
  return Task(
109
+ config=self.tasks_config["report_writing_task"],
110
+ context=[self.city_research_task(), self.news_analysis_task()],
111
  )
112
 
113
  @crew
 
118
  agents=self.agents,
119
  tasks=self.tasks,
120
  process=Process.sequential,
 
121
  verbose=True,
122
  )
src/report_genie/main.py CHANGED
@@ -2,7 +2,7 @@
2
  import sys
3
  import warnings
4
 
5
- from report_genie.crew import ExpresslyServer
6
  import dotenv
7
 
8
  warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
@@ -16,13 +16,11 @@ def run():
16
  """
17
 
18
  inputs = {
19
- "prompt": "I want to thanks DeepLearning and John from the crewAI for this amazing course..",
20
- "format": "Email",
21
- "tone": "Friendly",
22
- "target_audience": "",
23
  }
24
 
25
- ExpresslyServer().crew().kickoff(inputs=inputs)
26
 
27
 
28
  def train():
@@ -31,7 +29,7 @@ def train():
31
  """
32
  inputs = {"topic": "AI LLMs"}
33
  try:
34
- ExpresslyServer().crew().train(
35
  n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs
36
  )
37
 
@@ -44,7 +42,7 @@ def replay():
44
  Replay the crew execution from a specific task.
45
  """
46
  try:
47
- ExpresslyServer().crew().replay(task_id=sys.argv[1])
48
 
49
  except Exception as e:
50
  raise Exception(f"An error occurred while replaying the crew: {e}")
@@ -56,7 +54,7 @@ def test():
56
  """
57
  inputs = {"topic": "AI LLMs"}
58
  try:
59
- ExpresslyServer().crew().test(
60
  n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs
61
  )
62
 
 
2
  import sys
3
  import warnings
4
 
5
+ from report_genie.crew import ReportGenieServer
6
  import dotenv
7
 
8
  warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
 
16
  """
17
 
18
  inputs = {
19
+ "city_name": "Seattle",
20
+ "industry_name": "Hardware Companies",
 
 
21
  }
22
 
23
+ ReportGenieServer().crew().kickoff(inputs=inputs)
24
 
25
 
26
  def train():
 
29
  """
30
  inputs = {"topic": "AI LLMs"}
31
  try:
32
+ ReportGenieServer().crew().train(
33
  n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs
34
  )
35
 
 
42
  Replay the crew execution from a specific task.
43
  """
44
  try:
45
+ ReportGenieServer().crew().replay(task_id=sys.argv[1])
46
 
47
  except Exception as e:
48
  raise Exception(f"An error occurred while replaying the crew: {e}")
 
54
  """
55
  inputs = {"topic": "AI LLMs"}
56
  try:
57
+ ReportGenieServer().crew().test(
58
  n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs
59
  )
60
 
src/report_genie/schemas/schema.py CHANGED
@@ -20,3 +20,8 @@ class ChatInput(BaseModel):
20
 
21
  class ChatOutput(BaseModel):
22
  result: str = Field(..., description="The transformed text response")
 
 
 
 
 
 
20
 
21
  class ChatOutput(BaseModel):
22
  result: str = Field(..., description="The transformed text response")
23
+
24
+ class GetCityInfoInput(BaseModel):
25
+ """Input schema for MyCustomTool."""
26
+ city: str = Field(..., description="City name")
27
+ industry: str = Field(..., description="Industry name")
src/report_genie/tools/neo4j_tools.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from crewai.tools import tool
2
+ from neo4j import GraphDatabase
3
+ import os
4
+
5
+ ## Neo4j connection details
6
+ NEO_DB_URI = os.getenv("NEO_DB_URI")
7
+ NEO_DB_USERNAME = os.getenv("NEO_DB_USERNAME")
8
+ NEO_DB_PWD = os.getenv("NEO_DB_USERNAME")
9
+ NEO_DB_DATABSE = os.getenv("NEO_DB_DATABSE")
10
+
11
+ AUTH = (NEO_DB_USERNAME, NEO_DB_PWD)
12
+ driver = GraphDatabase.driver(NEO_DB_URI, auth=AUTH)
13
+
14
+ @tool("get_city_info")
15
+ def get_city_info(city_name: str, industry_name: str) -> list:
16
+ """
17
+ Get various information about a city and industry
18
+
19
+ Given a city and an industry, fetch the following information from the graph database:
20
+ - The number of organizations in the given industry that are in the given city
21
+ - The number of public companies in the given industry that are in the given city
22
+ - The total revenue of all companies in the given industry that are in the given city
23
+ - The 5 companies in the given industry that are in the given city with the most employees
24
+
25
+ :param city_name: The name of the city
26
+ :param industry_name: The name of the industry
27
+ :return: A list of dictionaries, each containing the above information
28
+ """
29
+ data, _, _ = driver.execute_query("""MATCH (c:City)<-[:IN_CITY]-(o:Organization)-[:HAS_CATEGORY]->(i:IndustryCategory)
30
+ WHERE c.name =
31
+ industry
32
+ WITH o
33
+ ORDER BY o.nbrEmployees DESC
34
+ RETURN count(o) AS organizationCount,
35
+ sum(CASE WHEN o.isPublic THEN 1 ELSE 0 END) AS publicCompanies,
36
+ sum(o.revenue) AS combinedRevenue,
37
+ collect(CASE WHEN o.nbrEmployees IS NOT NULL THEN o END)[..5] AS topFiveOrganizations""", city=city_name, industry=industry_name)
38
+ return [el.data() for el in data]
39
+
40
+
41
+ @tool("get_news")
42
+ def get_news(company: str) -> list:
43
+ """
44
+ Get the 5 most recent news articles mentioning a given company.
45
+
46
+ :param company: The name of the company to search for
47
+ :return: A list of dictionaries with the following keys:
48
+ - title: The title of the article
49
+ - date: The date the article was published
50
+ - sentiment: The sentiment of the article (positive, negative, or neutral)
51
+ - chunks: A list of strings containing the text of the article
52
+ """
53
+ data, _, _ = driver.execute_query("""MATCH (c:Chunk)<-[:HAS_CHUNK]-(a:Article)-[:MENTIONS]->(o:Organization)
54
+ WHERE o.name = $company AND a.date IS NOT NULL
55
+ WITH c, a
56
+ ORDER BY a.date DESC
57
+ LIMIT 5
58
+ RETURN a.title AS title, a.date AS date, a.sentiment AS sentiment, collect(c.text) AS chunks""", company=company)
59
+ return [el.data() for el in data]
src/report_genie/utils/utils.py CHANGED
@@ -14,7 +14,7 @@ def load_json_data(file_name: str, file_path: str) -> dict:
14
  """
15
 
16
  file_path = f"{file_path}/{file_name}"
17
- with open(file_path, "r") as file:
18
  return json.load(file)
19
 
20
 
 
14
  """
15
 
16
  file_path = f"{file_path}/{file_name}"
17
+ with open(file_path, "r", encoding="utf-8-sig") as file:
18
  return json.load(file)
19
 
20
 
uv.lock CHANGED
@@ -2318,6 +2318,18 @@ wheels = [
2318
  { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
2319
  ]
2320
 
 
 
 
 
 
 
 
 
 
 
 
 
2321
  [[package]]
2322
  name = "networkx"
2323
  version = "3.4.2"
@@ -3619,12 +3631,14 @@ source = { editable = "." }
3619
  dependencies = [
3620
  { name = "crewai", extra = ["tools"] },
3621
  { name = "gradio" },
 
3622
  ]
3623
 
3624
  [package.metadata]
3625
  requires-dist = [
3626
  { name = "crewai", extras = ["tools"], specifier = ">=0.86.0,<1.0.0" },
3627
  { name = "gradio", specifier = ">=5.12.0,<5.13.0" },
 
3628
  ]
3629
 
3630
  [[package]]
 
2318
  { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
2319
  ]
2320
 
2321
+ [[package]]
2322
+ name = "neo4j"
2323
+ version = "5.28.1"
2324
+ source = { registry = "https://pypi.org/simple" }
2325
+ dependencies = [
2326
+ { name = "pytz" },
2327
+ ]
2328
+ sdist = { url = "https://files.pythonhosted.org/packages/4b/20/733dac16f7cedc80b23093415822c9763302519cba0e7c8bcdb5c01fc512/neo4j-5.28.1.tar.gz", hash = "sha256:ae8e37a1d895099062c75bc359b2cce62099baac7be768d0eba7180c1298e214", size = 231094 }
2329
+ wheels = [
2330
+ { url = "https://files.pythonhosted.org/packages/6a/57/94225fe5e9dabdc0ff60c88cbfcedf11277f4b34e7ab1373d3e62dbdd207/neo4j-5.28.1-py3-none-any.whl", hash = "sha256:6755ef9e5f4e14b403aef1138fb6315b120631a0075c138b5ddb2a06b87b09fd", size = 312258 },
2331
+ ]
2332
+
2333
  [[package]]
2334
  name = "networkx"
2335
  version = "3.4.2"
 
3631
  dependencies = [
3632
  { name = "crewai", extra = ["tools"] },
3633
  { name = "gradio" },
3634
+ { name = "neo4j" },
3635
  ]
3636
 
3637
  [package.metadata]
3638
  requires-dist = [
3639
  { name = "crewai", extras = ["tools"], specifier = ">=0.86.0,<1.0.0" },
3640
  { name = "gradio", specifier = ">=5.12.0,<5.13.0" },
3641
+ { name = "neo4j", specifier = ">=5.28.1,<6.0.0" },
3642
  ]
3643
 
3644
  [[package]]