|
import streamlit as st |
|
import requests |
|
import os |
|
from typing import Literal, List |
|
from tavily import TavilyClient |
|
from pydantic import BaseModel |
|
from ollama import chat |
|
from dotenv import load_dotenv |
|
import instructor |
|
import logging |
|
from together import Together |
|
import json |
|
|
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
ELEVEN_LABS_API_KEY = "sk_cc3fea7dcfd81744dcc51673fcd011e7315d4732bab408a7" |
|
TAVILY_API_KEY = "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" |
|
|
|
load_dotenv() |
|
|
|
def call_llm(prompt): |
|
client = Together(api_key = "aa77adf5b5adaefe8fb3e4a5a1e9bb4937ba9d5d362e03de2521631ab9dab07f") |
|
response = client.chat.completions.create( |
|
model="meta-llama/Llama-3.3-70B-Instruct-Turbo", |
|
messages=[ |
|
{ |
|
"role": "user", |
|
"content": prompt |
|
} |
|
] |
|
) |
|
response = response.choices[0].message.content |
|
|
|
return response |
|
|
|
def fetch_from_web(query): |
|
tavily_client = TavilyClient(api_key=TAVILY_API_KEY) |
|
response = tavily_client.search( |
|
query, |
|
include_raw_content=True, |
|
max_results=10, |
|
topic="news", |
|
search_depth= "basic" |
|
) |
|
return {"sources": response['results']} |
|
|
|
class Sentiment(BaseModel): |
|
summary: str |
|
reasoning: str |
|
topics: List[str] |
|
sentiment: Literal['positive', 'negative', 'neutral'] |
|
|
|
def analyze_sentiment(article): |
|
sentiment_prompt = f""" |
|
Analyze the following news article about a company: |
|
|
|
1. **Summary**: Provide a comprehensive summary of the article's key points. |
|
|
|
2. **Sentiment Analysis**: |
|
- Classify the overall sentiment toward the company as: POSITIVE, NEGATIVE, or NEUTRAL |
|
- Support your classification with specific quotes, tone analysis, and factual evidence from the article |
|
- Explain your reasoning for this sentiment classification in 2 to 3 lines. |
|
|
|
3. **Key Topics**: |
|
- Identify 3-5 main topics discussed in the article |
|
- Only give the name of the topics |
|
|
|
Be as detailed and objective as possible in your reasoning. |
|
|
|
Article Title: {article['title']} |
|
|
|
Article: {article['raw_content']} |
|
""" |
|
|
|
try: |
|
client = Together(api_key = "aa77adf5b5adaefe8fb3e4a5a1e9bb4937ba9d5d362e03de2521631ab9dab07f") |
|
|
|
extract = client.chat.completions.create( |
|
messages=[ |
|
{ |
|
"role": "user", |
|
"content": sentiment_prompt, |
|
}, |
|
], |
|
model="meta-llama/Llama-3.3-70B-Instruct-Turbo", |
|
response_format={ |
|
"type": "json_object", |
|
"schema": Sentiment.model_json_schema(), |
|
}, |
|
) |
|
|
|
output = json.loads(extract.choices[0].message.content) |
|
|
|
final_dict = { |
|
"title": article["title"], |
|
"summary": output.get("summary"), |
|
"reasoning": output.get("reasoning"), |
|
"topics": output.get("topics"), |
|
"sentiment": output.get("sentiment") |
|
} |
|
|
|
return final_dict |
|
|
|
except Exception as e: |
|
logger.error(f"Error parsing sentiment output: {e}") |
|
return None |
|
|
|
def generate_comparative_sentiment(articles): |
|
sentiment_counts = {"Positive": 0, "Negative": 0, "Neutral": 0} |
|
|
|
for article in articles: |
|
sentiment = article.get("sentiment", "").lower() |
|
if sentiment == "positive": |
|
sentiment_counts["Positive"] += 1 |
|
elif sentiment == "negative": |
|
sentiment_counts["Negative"] += 1 |
|
elif sentiment == "neutral": |
|
sentiment_counts["Neutral"] += 1 |
|
|
|
all_topics = [] |
|
for article in articles: |
|
all_topics.extend(article.get("topics", [])) |
|
|
|
unique_topics = set(all_topics) |
|
|
|
topic_counts = {} |
|
|
|
for topic in unique_topics: |
|
count = all_topics.count(topic) |
|
topic_counts[topic] = count |
|
|
|
common_topics = [topic for topic, count in topic_counts.items() if count > 1] |
|
unique_topics = {} |
|
|
|
for i, article in enumerate(articles): |
|
article_topics = set(article.get("topics", [])) |
|
for j, other_article in enumerate(articles): |
|
if i != j: |
|
other_topics = set(other_article.get("topics", [])) |
|
unique_topics[f"Unique Topics in Article {i+1}"] = list(article_topics - other_topics) |
|
|
|
comparative_sentiment = { |
|
"Sentiment Distribution": sentiment_counts, |
|
"Coverage Differences": "coverage_differences", |
|
"Topic Overlap": { |
|
"Common Topics": common_topics, |
|
"Unique Topics in Article 1": unique_topics.get("Unique Topics in Article 1", []), |
|
"Unique Topics in Article 2": unique_topics.get("Unique Topics in Article 2", []), |
|
"Unique Topics in Article 3": unique_topics.get("Unique Topics in Article 3", []), |
|
"Unique Topics in Article 4": unique_topics.get("Unique Topics in Article 4", []), |
|
"Unique Topics in Article 5": unique_topics.get("Unique Topics in Article 5", []), |
|
"Unique Topics in Article 6": unique_topics.get("Unique Topics in Article 6", []), |
|
"Unique Topics in Article 7": unique_topics.get("Unique Topics in Article 7", []), |
|
"Unique Topics in Article 8": unique_topics.get("Unique Topics in Article 8", []), |
|
"Unique Topics in Article 9": unique_topics.get("Unique Topics in Article 9", []), |
|
"Unique Topics in Article 10": unique_topics.get("Unique Topics in Article 10", []) |
|
}, |
|
} |
|
|
|
return comparative_sentiment |
|
|
|
def get_summaries_by_sentiment(articles): |
|
pos_sum = [] |
|
neg_sum = [] |
|
neutral_sum = [] |
|
|
|
for article in articles: |
|
sentiment = article.get("sentiment", "").lower() |
|
title = article.get("title", "No Title") |
|
summary = article.get("summary", "No Summary") |
|
|
|
article_text = f'Title: {title}\nSummary: {summary}' |
|
|
|
if sentiment == "positive": |
|
pos_sum.append(article_text) |
|
elif sentiment == "negative": |
|
neg_sum.append(article_text) |
|
elif sentiment == "neutral": |
|
neutral_sum.append(article_text) |
|
|
|
pos_sum = "\n\n".join(pos_sum) if pos_sum else "No positive articles available." |
|
neg_sum = "\n\n".join(neg_sum) if neg_sum else "No negative articles available." |
|
neutral_sum = "\n\n".join(neutral_sum) if neutral_sum else "No neutral articles available." |
|
|
|
return pos_sum, neg_sum, neutral_sum |
|
|
|
def comparative_analysis(pos_sum, neg_sum, neutral_sum): |
|
prompt = f""" |
|
Perform a detailed comparative analysis of the sentiment across three categories of articles (Positive, Negative, and Neutral) about a specific company. Address the following aspects: |
|
|
|
1. **Sentiment Breakdown**: Identify how each category (positive, negative, and neutral) portrays the company. Highlight the language, tone, and emotional cues that shape the sentiment. |
|
|
|
2. **Key Themes and Topics**: Compare the primary themes and narratives within each sentiment group. What aspects of the company's operations, performance, or reputation does each category focus on? |
|
|
|
3. **Perceived Company Image**: Analyze how each sentiment type influences public perception of the company. What impression is created by positive vs. negative vs. neutral coverage? |
|
|
|
4. **Bias and Framing**: Evaluate whether any of the articles reflect explicit biases or specific agendas regarding the company. Are there patterns in how the company is framed across different sentiments? |
|
|
|
5. **Market or Stakeholder Impact**: Discuss potential effects on stakeholders (e.g., investors, customers, regulators) based on the sentiment of each article type. |
|
|
|
6. **Comparative Insights**: Provide a concise summary of the major differences and commonalities between the three sentiment groups. What overall narrative emerges about the company? |
|
|
|
### Positive Articles: |
|
{pos_sum} |
|
|
|
### Negative Articles: |
|
{neg_sum} |
|
|
|
### Neutral Articles: |
|
{neutral_sum} |
|
""" |
|
|
|
output = call_llm(prompt) |
|
return output |
|
|
|
def generate_final_report(pos_sum, neg_sum, neutral_sum, comparative_sentiment): |
|
final_report_prompt = f""" |
|
Corporate News Sentiment Analysis Report: |
|
|
|
### 1. Executive Summary |
|
- Overview of sentiment distribution: {comparative_sentiment["Sentiment Distribution"]['Positive']} positive, {comparative_sentiment["Sentiment Distribution"]['Negative']} negative, {comparative_sentiment["Sentiment Distribution"]['Neutral']} neutral. |
|
- Highlight the dominant narrative shaping the company's perception. |
|
- Summarize key drivers behind positive and negative sentiments. |
|
|
|
### 2. Media Coverage Analysis |
|
- Identify major news sources covering the company. |
|
- Highlight patterns in coverage across platforms (e.g., frequency, timing). |
|
- Identify whether media sentiment shifts over time. |
|
|
|
### 3. Sentiment Breakdown |
|
- **Positive Sentiment:** |
|
* Titles and sources: {pos_sum} |
|
* Key themes, notable quotes, and focal areas (e.g., product, leadership). |
|
- **Negative Sentiment:** |
|
* Titles and sources: {neg_sum} |
|
* Key themes, notable quotes, and areas of concern. |
|
- **Neutral Sentiment:** |
|
* Titles and sources: {neutral_sum} |
|
* Key themes and neutral narratives. |
|
|
|
### 4. Narrative Analysis |
|
- Identify primary storylines about the company. |
|
- Analyze how the company is positioned (positive, neutral, negative). |
|
- Detect shifts or emerging narratives over time. |
|
|
|
### 5. Key Drivers of Sentiment |
|
- Identify specific events, announcements, or actions driving media sentiment. |
|
- Evaluate sentiment linked to industry trends vs. company-specific factors. |
|
- Highlight company strengths and weaknesses based on media portrayal. |
|
|
|
### 6. Competitive Context |
|
- Identify competitor comparisons. |
|
- Analyze how media sentiment about the company compares to industry standards. |
|
- Highlight competitive advantages or concerns raised by the media. |
|
|
|
### 7. Stakeholder Perspective |
|
- Identify how key stakeholders (e.g., investors, customers, regulators) are represented. |
|
- Analyze stakeholder concerns and reputation risks/opportunities. |
|
|
|
### 8. Recommendations |
|
- Suggest strategies to mitigate negative sentiment. |
|
- Recommend approaches to amplify positive narratives. |
|
- Provide messaging suggestions for future announcements. |
|
|
|
### 9. Appendix |
|
- Full article details (title, publication, date, author, URL). |
|
- Media monitoring metrics (reach, engagement, etc.). |
|
""" |
|
|
|
response = call_llm(final_report_prompt) |
|
|
|
return response |
|
|
|
def translate(report, target_language): |
|
|
|
translation_prompt = f""" |
|
Translate the following corporate sentiment analysis report into {target_language}: |
|
|
|
{report} |
|
|
|
Ensure the translation maintains professional tone and structure while accurately conveying key insights and details. |
|
""" |
|
translation = call_llm(translation_prompt) |
|
return translation |
|
|
|
def text_to_speech(text): |
|
url = "https://api.elevenlabs.io/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb?output_format=mp3_44100_128" |
|
|
|
model_id = "eleven_multilingual_v2" |
|
output_file = "output.mp3" |
|
api_key = "sk_a927222500aab9665f83f078b92e833e7ec1389ee68238c0" |
|
|
|
headers = { |
|
"xi-api-key": api_key, |
|
"Content-Type": "application/json" |
|
} |
|
|
|
payload = { |
|
"text": text, |
|
"model_id": model_id |
|
} |
|
|
|
response = requests.post(url, headers=headers, json=payload) |
|
|
|
|
|
if response.status_code == 200: |
|
|
|
return response.content |
|
|
|
else: |
|
print(f"❌ Error: {response.status_code} - {response.text}") |
|
return None |
|
|
|
|
|
st.title("Company Sentiment Analyzer") |
|
|
|
company_name = st.text_input("Enter Company Name") |
|
target_language = st.text_input("Enter Target Language for Translation") |
|
|
|
|
|
|
|
if st.button("Fetch Sentiment Data"): |
|
try: |
|
logger.info(f"Fetching web results for {company_name}") |
|
web_results = fetch_from_web(company_name) |
|
|
|
if "sources" not in web_results: |
|
st.error("No sources found.") |
|
else: |
|
sentiment_output = [ |
|
analyze_sentiment(article) |
|
for article in web_results["sources"] |
|
] |
|
sentiment_output = [s for s in sentiment_output if s is not None] |
|
logger.info(f"Generating comparative sentiment") |
|
comparative_sentiment = generate_comparative_sentiment(sentiment_output) |
|
|
|
logger.info(f"Summarizing report by sentiment") |
|
positive_summary, negative_summary, neutral_summary = get_summaries_by_sentiment( |
|
sentiment_output |
|
) |
|
|
|
logger.info(f"Generating final summary") |
|
final_report = generate_final_report( |
|
positive_summary, |
|
negative_summary, |
|
neutral_summary, |
|
comparative_sentiment |
|
) |
|
|
|
logger.info(f"Translating Report") |
|
hindi_translation = translate(final_report, target_language= target_language) |
|
|
|
logger.info(f"Generating Speech from Text") |
|
|
|
|
|
output_dict = { |
|
"company_name": company_name, |
|
"articles": sentiment_output, |
|
"comparative_sentiment": comparative_sentiment, |
|
"final_report": final_report, |
|
"hindi_translation": hindi_translation, |
|
"audio_text": "", |
|
} |
|
|
|
st.subheader("Company Name") |
|
st.write(output_dict.get("company_name")) |
|
|
|
st.subheader("Final Report") |
|
st.write(output_dict.get("final_report")) |
|
|
|
st.subheader("Translated Report") |
|
st.write(output_dict.get("hindi_translation", "Please Check Your Internet Connection")) |
|
|
|
st.subheader("Speech To Text") |
|
st.write("Request Timed Out Please Check Your Internet Connection") |
|
|
|
except requests.exceptions.RequestException as e: |
|
st.error(f"Error fetching data: {e}") |
|
logger.error(f"Error fetching data: {e}") |
|
|