Spaces:
Sleeping
Sleeping
deepakpant commited on
Commit ·
c34a4b0
1
Parent(s): cba9a3b
Added gradio UI
Browse files- .env.example +5 -1
- knowledge/cities.json +104 -0
- knowledge/content_style_mapping.json +0 -48
- knowledge/format.json +0 -46
- knowledge/industries.json +54 -0
- knowledge/target_audience.json +0 -59
- knowledge/tone.json +0 -36
- pyproject.toml +3 -1
- src/report_genie/app.py +36 -110
- src/report_genie/config/agents.yaml +27 -7
- src/report_genie/config/tasks.yaml +21 -36
- src/report_genie/crew.py +61 -103
- src/report_genie/main.py +7 -9
- src/report_genie/schemas/schema.py +5 -0
- src/report_genie/tools/neo4j_tools.py +59 -0
- src/report_genie/utils/utils.py +1 -1
- uv.lock +14 -0
.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
|
|
|
|
| 4 |
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 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 |
-
|
| 29 |
-
|
| 30 |
-
tone = ""
|
| 31 |
-
elif active_tab == "format_tone":
|
| 32 |
-
target_audience = ""
|
| 33 |
-
else:
|
| 34 |
-
raise ValueError("Invalid active_tab value")
|
| 35 |
|
|
|
|
|
|
|
| 36 |
inputs = {
|
| 37 |
-
"
|
| 38 |
-
"
|
| 39 |
-
"format": format,
|
| 40 |
-
"tone": tone,
|
| 41 |
}
|
| 42 |
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
-
if
|
| 46 |
result = "Please check the inputs and try again. If the issue persists, contact support."
|
| 47 |
else:
|
| 48 |
-
result =
|
| 49 |
|
| 50 |
return result
|
| 51 |
|
| 52 |
|
|
|
|
| 53 |
with gr.Blocks() as app:
|
| 54 |
-
gr.Markdown("#
|
| 55 |
-
|
|
|
|
| 56 |
with gr.Row():
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 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 |
-
|
| 2 |
role: >
|
| 3 |
-
|
| 4 |
goal: >
|
| 5 |
-
|
| 6 |
backstory: >
|
| 7 |
-
You
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 2 |
description: >
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 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 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
---
|
| 35 |
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
expected_output: >
|
| 38 |
-
|
| 39 |
-
|
| 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
|
| 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 |
-
@
|
| 38 |
-
def
|
| 39 |
-
self, inputs: Optional[Dict[str, Any]]
|
| 40 |
-
) -> Optional[Dict[str, Any]]:
|
| 41 |
"""
|
| 42 |
-
|
| 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 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 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
|
| 120 |
"""
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 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["
|
| 134 |
llm=self.llm,
|
| 135 |
-
knowledge_source=[self.json_knowledge_source],
|
| 136 |
verbose=True,
|
| 137 |
)
|
| 138 |
|
| 139 |
@task
|
| 140 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
"""
|
| 142 |
-
|
|
|
|
|
|
|
| 143 |
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
-
|
| 148 |
-
|
| 149 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
return Task(
|
| 151 |
-
config=self.tasks_config["
|
|
|
|
| 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
|
| 6 |
import dotenv
|
| 7 |
|
| 8 |
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
|
@@ -16,13 +16,11 @@ def run():
|
|
| 16 |
"""
|
| 17 |
|
| 18 |
inputs = {
|
| 19 |
-
"
|
| 20 |
-
"
|
| 21 |
-
"tone": "Friendly",
|
| 22 |
-
"target_audience": "",
|
| 23 |
}
|
| 24 |
|
| 25 |
-
|
| 26 |
|
| 27 |
|
| 28 |
def train():
|
|
@@ -31,7 +29,7 @@ def train():
|
|
| 31 |
"""
|
| 32 |
inputs = {"topic": "AI LLMs"}
|
| 33 |
try:
|
| 34 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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]]
|