Skip to content

Commit b6eead9

Browse files
committed
Add brand monitoring code
1 parent 79e93bc commit b6eead9

File tree

27 files changed

+1212
-0
lines changed

27 files changed

+1212
-0
lines changed

brand-monitoring/README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Brand monitoring flow using DeepSeek-R1, CrewAI and BrightData
2+
3+
This project implements an automated brand monitoring system using AI agents. We use the following tools to build this:
4+
- [Bright Data](https://brdta.com/dailydoseofds) is used to scrape the web.
5+
- CrewAI to build the Agentic workflow.
6+
- DeepSeek-R1 as the LLM.
7+
8+
The brand monitoring output is shown here: [Sample output](brand-monitoring-demo.mp4)
9+
10+
---
11+
## Setup and installations
12+
13+
**Get BrightData API Key**:
14+
- Go to [Bright Data](https://brdta.com/dailydoseofds) and sign up for an account.
15+
- Select "Proxies & Scraping" and create a new "SERP API"
16+
- Select "Native proxy-based access"
17+
- You will find your username and password there.
18+
- Store it in the .env file of the src/ folder (after renaming the .env.example file to .env)
19+
20+
```
21+
BRIGHT_DATA_USERNAME="..."
22+
BRIGHT_DATA_PASSWORD="..."
23+
```
24+
25+
- Also get the Bright Data API key from your dashboard.
26+
27+
```
28+
BRIGHT_DATA_API_KEY="..."
29+
```
30+
31+
**Setup Ollama**:
32+
```bash
33+
# setup ollama on linux
34+
curl -fsSL https://ollama.com/install.sh | sh
35+
# pull deepseek-r1 model
36+
ollama pull deepseek-r1
37+
```
38+
39+
40+
**Install Dependencies**:
41+
Ensure you have Python 3.11 or later installed.
42+
```bash
43+
pip install ollama crewai crewai-tools streamlit
44+
```
45+
46+
---
47+
48+
## Run the project
49+
50+
Finally, head over to this folder:
51+
```
52+
cd brand_monitoring_flow/src
53+
```
54+
55+
and run the project by running the following command:
56+
57+
```bash
58+
streamlit run brand_monitoring_app.py
59+
```
60+
61+
---
62+
63+
## 📬 Stay Updated with Our Newsletter!
64+
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
65+
66+
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
67+
68+
---
69+
70+
## Contribution
71+
72+
Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
12.3 MB
Binary file not shown.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
BRIGHT_DATA_USERNAME="..."
2+
BRIGHT_DATA_PASSWORD="..."
3+
BRIGHT_DATA_API_KEY="..."
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[project]
2+
name = "brand_monitoring_flow"
3+
version = "0.1.0"
4+
description = "brand-monitoring-flow using crewAI"
5+
authors = [{ name = "Your Name", email = "you@example.com" }]
6+
requires-python = ">=3.10,<3.13"
7+
dependencies = [
8+
"crewai[tools]>=0.102.0,<1.0.0",
9+
]
10+
11+
[project.scripts]
12+
kickoff = "brand_monitoring_flow.main:kickoff"
13+
plot = "brand_monitoring_flow.main:plot"
14+
15+
[build-system]
16+
requires = ["hatchling"]
17+
build-backend = "hatchling.build"
18+
19+
[tool.crewai]
20+
type = "flow"
12.1 KB
Loading
217 KB
Loading
8.04 KB
Loading
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import streamlit as st
2+
import base64
3+
import gc
4+
import brand_monitoring_flow.main
5+
6+
# ===========================
7+
# Streamlit Setup
8+
# ===========================
9+
10+
if "response" not in st.session_state:
11+
st.session_state.response = None
12+
13+
if "flow" not in st.session_state:
14+
st.session_state.flow = None
15+
16+
if "deep_seek_image" not in st.session_state:
17+
st.session_state.deep_seek_image = base64.b64encode(open("assets/deep-seek.png", "rb").read()).decode()
18+
if "brightdata_image" not in st.session_state:
19+
st.session_state.brightdata_image = base64.b64encode(open("assets/brightdata.png", "rb").read()).decode()
20+
21+
def reset_analysis():
22+
st.session_state.response = None
23+
st.session_state.flow = None
24+
gc.collect()
25+
26+
27+
def start_analysis():
28+
st.markdown("""
29+
# Brand Monitoring powered by <img src="data:image/png;base64,{}" width="180" style="vertical-align: -7px;"> & <img src="data:image/png;base64,{}" width="180" style="vertical-align: -10px;">
30+
""".format(
31+
st.session_state.deep_seek_image,
32+
st.session_state.brightdata_image
33+
), unsafe_allow_html=True)
34+
35+
# Create a placeholder for status updates
36+
status_placeholder = st.empty()
37+
38+
with status_placeholder.container():
39+
if st.session_state.flow is None:
40+
status_placeholder.info('Initializing brand monitoring flow...')
41+
st.session_state.flow = brand_monitoring_flow.main.BrandMonitoringFlow()
42+
43+
st.session_state.flow.state.brand_name = st.session_state.brand_name
44+
st.session_state.flow.state.total_results = st.session_state.total_results
45+
46+
# You can update the status for different phases
47+
status_placeholder.info('Scraping latest data about {} and analyzing with AI Agents...'.format(st.session_state.brand_name))
48+
st.session_state.flow.kickoff()
49+
50+
# Store the results
51+
status_placeholder.success('Analysis complete! Displaying results...')
52+
st.session_state.response = st.session_state.flow.state
53+
else:
54+
st.session_state.response = st.session_state.flow.state
55+
56+
# Clear the status message after completion
57+
status_placeholder.empty()
58+
59+
# ===========================
60+
# Sidebar
61+
# ===========================
62+
with st.sidebar:
63+
st.header("Brand Monitoring Settings")
64+
65+
# Brand name input
66+
st.session_state.brand_name = st.text_input(
67+
"Company/Brand Name",
68+
value="Hugging Face" if "brand_name" not in st.session_state else st.session_state.brand_name
69+
)
70+
71+
# Number of search results
72+
st.session_state.total_results = st.number_input(
73+
"Total Search Results",
74+
min_value=1,
75+
max_value=50,
76+
value=15,
77+
step=1
78+
)
79+
80+
st.divider()
81+
82+
# Analysis buttons
83+
col1, col2 = st.columns(2)
84+
with col1:
85+
st.button("Start Analysis 🚀", type="primary", on_click=start_analysis)
86+
with col2:
87+
st.button("Reset", on_click=reset_analysis)
88+
89+
# ===========================
90+
# Main Content Area
91+
# ===========================
92+
93+
# Move the header inside a container to ensure it stays at the top
94+
95+
if st.session_state.response is None:
96+
header_container = st.container()
97+
with header_container:
98+
st.markdown("""
99+
# Brand Monitoring powered by <img src="data:image/png;base64,{}" width="180" style="vertical-align: -7px;"> & <img src="data:image/png;base64,{}" width="180" style="vertical-align: -10px;">
100+
""".format(
101+
st.session_state.deep_seek_image,
102+
st.session_state.brightdata_image
103+
), unsafe_allow_html=True)
104+
105+
if st.session_state.response:
106+
try:
107+
response = st.session_state.response
108+
109+
# LinkedIn Results
110+
if response.linkedin_crew_response:
111+
st.markdown("## 💼 LinkedIn Mentions")
112+
for post in response.linkedin_crew_response.pydantic.content:
113+
with st.expander(f"📝 {post.post_title}"):
114+
st.markdown(f"**Source:** [{post.post_link}]({post.post_link})")
115+
for line in post.content_lines:
116+
st.markdown(f"- {line}")
117+
118+
# Instagram Results
119+
if response.instagram_crew_response:
120+
st.markdown("## 📸 Instagram Mentions")
121+
for post in response.instagram_crew_response.pydantic.content:
122+
with st.expander(f"📝 {post.post_title}"):
123+
st.markdown(f"**Source:** [{post.post_link}]({post.post_link})")
124+
for line in post.content_lines:
125+
st.markdown(f"- {line}")
126+
127+
# YouTube Results
128+
if response.youtube_crew_response:
129+
st.markdown("## 🎥 YouTube Mentions")
130+
for video in response.youtube_crew_response.pydantic.content:
131+
with st.expander(f"📝 {video.video_title}"):
132+
st.markdown(f"**Source:** [{video.video_link}]({video.video_link})")
133+
for line in video.content_lines:
134+
st.markdown(f"- {line}")
135+
136+
# X/Twitter Results
137+
if response.x_crew_response:
138+
st.markdown("## 🐦 X/Twitter Mentions")
139+
for post in response.x_crew_response.pydantic.content:
140+
with st.expander(f"📝 {post.post_title}"):
141+
st.markdown(f"**Source:** [{post.post_link}]({post.post_link})")
142+
for line in post.content_lines:
143+
st.markdown(f"- {line}")
144+
145+
# Web Results
146+
if response.web_crew_response:
147+
st.markdown("## 🌐 Web Mentions")
148+
for page in response.web_crew_response.pydantic.content:
149+
with st.expander(f"📝 {page.page_title}"):
150+
st.markdown(f"**Source:** [{page.page_link}]({page.page_link})")
151+
for line in page.content_lines:
152+
st.markdown(f"- {line}")
153+
154+
except Exception as e:
155+
st.error(f"An error occurred while displaying results: {str(e)}")
156+
157+
# Footer
158+
st.markdown("---")
159+
st.markdown("Built with CrewAI, Bright Data and Streamlit")

brand-monitoring/brand_monitoring_flow/src/brand_monitoring_flow/__init__.py

Whitespace-only changes.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from crewai import Agent, Crew, Process, Task
2+
from crewai.project import CrewBase, agent, crew, task
3+
from pydantic import BaseModel, Field
4+
5+
class XWriterReport(BaseModel):
6+
post_title: str = Field(description="The title explaining how the brand was used in an individual post")
7+
post_link: str = Field(description="The link to the scraped content")
8+
content_lines: list[str] = Field(description="The bullet points within the scraped content that are relevant to the brand")
9+
10+
class XReport(BaseModel):
11+
content: list[XWriterReport] = Field(description=("A list of extracted content with title, the post link, "
12+
"and the bullet points within each unique post. "
13+
"The size of the output list will be the same as the number of posts in the input data.")
14+
)
15+
16+
llm = LLM(model="ollama/deepseek-r1")
17+
18+
19+
@CrewBase
20+
class XCrew:
21+
"""X Analysis Crew"""
22+
23+
agents_config = "config/agents.yaml"
24+
tasks_config = "config/tasks.yaml"
25+
26+
@agent
27+
def analysis_agent(self) -> Agent:
28+
return Agent(
29+
config=self.agents_config["analysis_agent"],
30+
llm=llm,
31+
)
32+
33+
@task
34+
def analysis_task(self) -> Task:
35+
return Task(
36+
config=self.tasks_config["analysis_task"],
37+
)
38+
39+
@agent
40+
def writer_agent(self) -> Agent:
41+
return Agent(
42+
config=self.agents_config["writer_agent"],
43+
llm=llm,
44+
)
45+
46+
@task
47+
def write_report_task(self) -> Task:
48+
return Task(
49+
config=self.tasks_config["write_report_task"],
50+
output_pydantic=XReport,
51+
)
52+
53+
@crew
54+
def crew(self) -> Crew:
55+
"""Creates the X Analysis Crew"""
56+
57+
return Crew(
58+
agents=self.agents,
59+
tasks=self.tasks,
60+
process=Process.sequential,
61+
verbose=True,
62+
)

0 commit comments

Comments
 (0)