351 lines
19 KiB
Python
351 lines
19 KiB
Python
import json
|
|
import requests
|
|
from config import WORD_COUNT, CATEGORY_LIMITS, KEYWORDS_OF_INTEREST, MODEL_NAME, CATEGORIES_OF_INTEREST, time_period
|
|
from urllib.parse import quote
|
|
from openai import OpenAI
|
|
from collections import defaultdict
|
|
import os
|
|
|
|
welcome_message_summary_template = """
|
|
{personality} Witaj w newsletterze {time_period}. Otrzymasz streszczenia wszystkiego, co będzie zawierał ten newsletter, podsumuj kilka interesujących fragmentów (nie więcej niż 3-4) w mniej niż 50 słowach, przedstaw temat newslettera. BĄDŹ zwięzły! Używaj jednak emotikonów. To tylko krótki wstęp! Nie wspominaj tutaj absolutnie wszystkiego, ponieważ spowoduje to przeciążenie informacyjne. Bądź krótki, zwięzły i nie ozdabiaj. NIE udostępniaj tutaj adresów URL.""".strip()
|
|
|
|
trending_keyword_summary_template = """{personality} Otrzymasz źródła jednego popularnego słowa kluczowego dla określonego okresu {time_period}, najpierw bardzo krótko wyjaśnij, czym jest to słowo kluczowe. Następnie powinieneś podsumować to, co zostało powiedziane, za pomocą {word_count} słów z dostarczonych danych, aby zrozumieć, DLACZEGO jest popularne (użyj odrobiny emotikonów). Upewnij się, że jest to dobrze sformatowane podsumowanie, a nie tylko nazywanie każdej rzeczy. Mów o tym, co się dzieje i dlaczego słowo kluczowe jest popularne na podstawie posiadanych danych. Powinno to płynąć płynnie, abyśmy mogli zrozumieć, co się dzieje. Nie upiększaj i nie uogólniaj. Chcemy jasnych i przydatnych informacji oraz przejść do sedna.
|
|
1. Bardzo ważne jest, aby szukać interesujących dyskusji i innowacyjnych przypadków użycia i wchodzić w SZCZEGÓŁY. Nie uogólniaj po prostu. Np. jeśli używają narzędzia do celów edukacyjnych, wyjaśnij, w jaki sposób go używają do celów edukacyjnych. Zawsze WYJAŚNIAJ JASNO.
|
|
2. Pamiętaj, aby przyjrzeć się nastrojom, aby zobaczyć, co ludzie lubią, a czego nie. Negatywne nastroje są bardzo ważne. Zrozumienie, jak i dlaczego ludzie mówią o słowie kluczowym. Musimy dokładnie analizować zachowania publiczne w odniesieniu do słowa kluczowego.
|
|
3. Zawsze podawaj mi adres URL na końcu bardzo interesującego i wyjątkowego przypadku użycia LUB intensywnej dyskusji. Lubię innowacyjne podejścia i gorące dyskusje.
|
|
4. Zawsze podsumowuj, zamiast od razu podawać źródła.
|
|
5. W swoim podsumowaniu wykorzystaj interesujące teksty, o których powinniśmy wiedzieć, a także teksty o wysokiej trafności.
|
|
6. Przejrzystość bez znaczników: komunikuj swoje ustalenia jasnym, prostym językiem. Nie używaj żadnego języka znaczników (takiego jak pogrubienie lub kursywa), aby upewnić się, że podsumowanie jest zoptymalizowane pod kątem komunikacji e-mailowej i czytelności na wszystkich platformach.
|
|
7. Liczba słów nie powinna nigdy być większa ani mniejsza niż {word_count} słów.
|
|
8. Używaj umiarkowanej liczby emotikonów, aby podsumowanie było bardziej angażujące.
|
|
9. Skróć informacje i upewnij się, że płynnie się przekazują. Np. jeśli potrzebujesz uwzględnić tylko kilka ważnych zdarzeń dla czytelności i wykluczyć inne, zrób to.
|
|
10. Ważne: NIGDY NIE UŻYWAJ JĘZYKA ZNACZNIKÓW.
|
|
11. Zawsze wprowadzaj, czym jest słowo kluczowe, na wypadek gdyby użytkownik nie zrozumiał, co to jest.
|
|
12. Zawsze używaj emotikonów, aby to zilustrować."
|
|
""".strip()
|
|
|
|
personality = "Jesteś botem technologicznym newslettera, który przeszukuje strony internetowe poświęcone technologii. Używaj minimalnej liczby słów, aby uzyskać maksymalny efekt. Przejdź od razu do sedna, używając jak najmniejszej liczby słów. Twoim zadaniem jest skondensowanie informacji, aby były łatwe do przyswojenia. Zawsze bądź zwięzły i konkretny. Nie upiększaj. Im mniej słów, tym lepiej. Twoje słowa powinny mieć znaczenie. Nigdy nie łam charakteru. Używaj emotikonów, aby zilustrować. Zawsze używaj emotikonów. I najważniejsze piszesz w języku polskim"
|
|
|
|
interest_keyword_summary_template = """
|
|
{personality} Otrzymasz źródła dla jednego słowa kluczowego dla określonego okresu czasu {time_period}, który śledzę, powinieneś podsumować to, co zostało powiedziane, za pomocą {word_count} słów z dostarczonych danych, aby pomóc nam zrozumieć ostatnie wydarzenia i dyskusje, które miały miejsce. Upewnij się, że stanie się to dobrze sformatowanym podsumowaniem, a nie tylko wymienianiem każdej rzeczy. Wyciągnij najciekawsze ostatnie wydarzenia i trendy. Powinno to płynąć płynnie, gdzie zrozumiemy, co dzieje się ze słowem kluczowym. Nie upiększaj i nie uogólniaj za bardzo. Chcemy jasnych i przydatnych informacji oraz bezpośrednio przechodzić do sedna.
|
|
1. Bardzo ważne jest, aby szukać interesujących dyskusji i innowacyjnych przypadków użycia i wchodzić w SZCZEGÓŁY tutaj. Nie uogólniaj po prostu. Np. jeśli używają narzędzia do celów edukacyjnych, wyjaśnij, jak używają go do celów edukacyjnych. Zawsze ROZWIJAJ JASNO.
|
|
2. Pamiętaj, aby przyjrzeć się nastrojom, aby zobaczyć, co ludzie lubią, a czego nie. Negatywne nastroje są bardzo ważne, aby je poruszyć. Zrozumienie, jak i dlaczego ludzie mówią o słowie kluczowym. Musimy dokładnie analizować zachowania publiczne za pomocą słowa kluczowego.
|
|
3. Zawsze podawaj mi adres URL na końcu bardzo interesującego i wyjątkowego przypadku użycia LUB intensywnej dyskusji. Lubię innowacyjne podejścia i gorące dyskusje.
|
|
4. Zawsze podsumowuj, zamiast od razu podawać źródła.
|
|
5. W swoim podsumowaniu wykorzystaj interesujące teksty, o których powinniśmy wiedzieć, a także teksty o wysokiej istotności.
|
|
6. Przejrzystość bez znaczników: komunikuj swoje ustalenia jasnym, prostym językiem. Nie używaj żadnego języka znaczników (takiego jak pogrubienie lub kursywa), aby upewnić się, że podsumowanie jest zoptymalizowane pod kątem komunikacji e-mailowej i czytelności na wszystkich platformach.
|
|
7. Liczba słów nie powinna nigdy być większa ani mniejsza niż {word_count} słów. Upewnij się, że faktycznie trzymasz się liczby słów.
|
|
9. Skróć informacje i upewnij się, że płynnie się przekazują. Np. jeśli musisz uwzględnić tylko kilka ważnych zdarzeń dla czytelności i wykluczyć inne, zrób to.
|
|
10. Ważne: NIGDY NIE UŻYWAJ JĘZYKA ZNACZNIKÓW.
|
|
11. Zawsze wprowadzaj, czym jest słowo kluczowe, na wypadek gdyby użytkownik nie zrozumiał, co to jest.
|
|
12. Zawsze używaj emotikonów, aby to zilustrować.
|
|
""".strip()
|
|
|
|
trending_category_summary_template = """
|
|
{personality} Otrzymasz podsumowania kilku słów kluczowych z określonej kategorii, które były popularne w określonym okresie czasu {time_period}, powinieneś podsumować niektóre rzeczy, które zostały powiedziane, w mniej niż 500 słowach z podanych podsumowań, aby pomóc nam zrozumieć, o czym będziemy mówić w podsumowaniach. Nie używaj więcej niż 50 słów w całym tekście. To jak wprowadzenie do określonej kategorii. Powinieneś poruszyć najciekawsze rzeczy, które się tutaj wydarzyły, zamiast generalizować. Powinno to być dobre wprowadzenie do tej sekcji dla określonej kategorii. Podaj faktyczne i zwięzłe podsumowanie bez żadnych nieformalnych powitań lub przesadnego języka. Trzymaj się kluczowych punktów i głównych argumentów obu stron dotyczących ich cech, wygody i opinii użytkowników. Unikaj wszelkich upiększeń i zachowaj profesjonalny ton. Nie zaczynaj akapitu żadnym językiem znaczników ani nie wprowadzaj samej nazwy kategorii. Użyj emotikonów do wizualizacji. NIE udostępniaj tutaj adresów URL.""".strip()
|
|
|
|
# Summary of keywords of interest
|
|
interest_category_summary_template = """
|
|
{personality} Otrzymasz podsumowania kilku słów kluczowych, które śledzimy w określonym przedziale czasowym {time_period}, powinieneś podsumować niektóre rzeczy, które zostały powiedziane w mniej niż 500 słowach z dostarczonych podsumowań, aby pomóc nam zrozumieć, o czym będziemy mówić. Możesz użyć dwóch akapitów. Nie używaj więcej niż 50 słów dla całego tekstu. To jest jak wprowadzenie do sekcji słów kluczowych, które śledzimy. Nie zaczynaj akapitu żadnym językiem znaczników ani nie wprowadzaj samej nazwy kategorii. Użyj emotikonów, aby to zwizualizować. NIE udostępniaj tutaj adresów URL.""".strip()
|
|
|
|
sourceMapping = {
|
|
"subjects": "Subjects",
|
|
"companies": "Companies & Organizations",
|
|
"ai": "AI Models & Assistants",
|
|
"frameworks": "Frameworks & Libraries",
|
|
"languages": "Languages & Syntax",
|
|
"concepts": "Concepts & Methods",
|
|
"tools": "Tools & Services",
|
|
"platforms": "Platforms & Search Engines",
|
|
"hardware": "Hardware & Systems",
|
|
"websites": "Websites & Applications",
|
|
"people": "People",
|
|
"bucket": "Bucket (other)"
|
|
}
|
|
|
|
def fetch_and_process_trending_items(time_period):
|
|
print("...fetch keywords")
|
|
final_results = []
|
|
|
|
inverse_mapping = {v: k for k, v in sourceMapping.items()}
|
|
|
|
for short_name, full_name in sourceMapping.items():
|
|
if full_name in CATEGORY_LIMITS:
|
|
limit = CATEGORY_LIMITS[full_name]
|
|
if limit == 0:
|
|
continue
|
|
|
|
params = {
|
|
"category": short_name,
|
|
"period": time_period,
|
|
"slim": "false",
|
|
"limit": limit,
|
|
"sort": "trending"
|
|
}
|
|
response = requests.get("https://public.api.safron.io/v2/keywords", params=params)
|
|
if response.status_code != 200:
|
|
print(f"Failed to fetch data for category '{full_name}'. Status code: {response.status_code}")
|
|
continue
|
|
|
|
data = response.json()
|
|
keywords = data.get("keywords", [])
|
|
sorted_keywords = sorted(
|
|
[item for item in keywords if item.get("trending", False)],
|
|
key=lambda x: x.get("engagement", 0),
|
|
reverse=True
|
|
)[:limit]
|
|
final_results.extend(sorted_keywords)
|
|
|
|
for keyword in KEYWORDS_OF_INTEREST:
|
|
encoded_keyword = quote(keyword)
|
|
params = {
|
|
"search": encoded_keyword,
|
|
"period": time_period,
|
|
"slim": "false",
|
|
"limit": 1
|
|
}
|
|
response = requests.get("https://public.api.safron.io/v2/keywords", params=params)
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
keywords = data.get("keywords", [])
|
|
if keywords:
|
|
final_results.extend(keywords)
|
|
|
|
for category in CATEGORIES_OF_INTEREST:
|
|
short_category = inverse_mapping.get(category)
|
|
if not short_category:
|
|
print(f"No short mapping found for category '{category}'. Skipping.")
|
|
continue
|
|
|
|
params = {
|
|
"category": short_category,
|
|
"period": time_period,
|
|
"slim": "false",
|
|
"limit": 2,
|
|
"sort": "top"
|
|
}
|
|
response = requests.get("https://public.api.safron.io/v2/keywords", params=params)
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
keywords = data.get("keywords", [])
|
|
final_results.extend(keywords)
|
|
|
|
return final_results
|
|
|
|
def transform_and_append_sources(final_results, max_retries=2):
|
|
print("...transform_and_append_sources")
|
|
transformed_results = []
|
|
|
|
for result in final_results:
|
|
source_ids = result.get("sources", [])
|
|
keyword = result.get("keyword", "Unknown")
|
|
|
|
if not source_ids:
|
|
print(f"Brak IDs zostało znalezionych dla słowa kluczowego: {keyword}.")
|
|
continue
|
|
|
|
attempts = 0
|
|
success = False
|
|
while attempts < max_retries and not success:
|
|
try:
|
|
params = {"limit": 110}
|
|
payload = {"ids": source_ids}
|
|
|
|
response = requests.post(
|
|
"https://public.api.safron.io/v2/sources",
|
|
params=params,
|
|
json=payload
|
|
)
|
|
response.raise_for_status()
|
|
articles_data = response.json()
|
|
articles_to_process = articles_data.get("articles", [])
|
|
|
|
if articles_to_process:
|
|
simplified_articles = [{
|
|
"text": article.get("text", ""),
|
|
"published": article.get("published", ""),
|
|
"source": article.get("source", ""),
|
|
"type": article.get("type", ""),
|
|
"sentiment_label": article.get("sentiment", ""),
|
|
"url": article.get("link", ""),
|
|
"relevance": article.get("engagement", "")
|
|
} for article in articles_to_process]
|
|
|
|
simplified_result = {
|
|
"keyword": keyword,
|
|
"count": result.get("count", 0),
|
|
"category": result.get("category", "Unknown"),
|
|
"trending": result.get("trending", False),
|
|
"sources": simplified_articles
|
|
}
|
|
transformed_results.append(simplified_result)
|
|
success = True
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
attempts += 1
|
|
print(f"Próba {attempts} nieudana dla słowa kluczowego '{keyword}'. Błąd: {e}")
|
|
|
|
if not success:
|
|
print(f"All retries failed for keyword '{keyword}'. This keyword will be removed from the results.")
|
|
|
|
return transformed_results
|
|
|
|
def summarize_sources(data, time_period):
|
|
print("...summarize_sources")
|
|
|
|
for result in data:
|
|
|
|
if result['trending']:
|
|
formatted_template = trending_keyword_summary_template.format(personality=personality, time_period=time_period, word_count=WORD_COUNT)
|
|
else:
|
|
formatted_template = interest_keyword_summary_template.format(personality=personality, time_period=time_period, word_count=WORD_COUNT)
|
|
|
|
sources_text = '\n'.join([f"Text: {source['text']}\nURL: {source['url']}\nSource: {source['source']}\nRelevance: {source['relevance']}\nAnalyzed Sentiment: {source['sentiment_label']}" for source in result['sources']])
|
|
message = f"Keyword: {result['keyword']}\n Kategoria: {result['category']}\n Podsumowanie źródła:\n{sources_text}"
|
|
summary = generate_response_openai(message, formatted_template)
|
|
result['summary'] = summary
|
|
|
|
return data
|
|
|
|
def generate_response_openai(message, system_template):
|
|
|
|
client = OpenAI();
|
|
# Remember to set your API key via export in the terminal
|
|
client.api_key = os.getenv("OPENAI_API_KEY")
|
|
|
|
response = client.chat.completions.create(
|
|
model=MODEL_NAME,
|
|
messages=[
|
|
{
|
|
"role": "system",
|
|
"content": system_template
|
|
},
|
|
{
|
|
"role": "user",
|
|
"content": message
|
|
}
|
|
],
|
|
temperature=0.8,
|
|
max_tokens=356,
|
|
top_p=1,
|
|
frequency_penalty=0,
|
|
presence_penalty=0
|
|
)
|
|
if response.choices:
|
|
return response.choices[0].message.content
|
|
else:
|
|
return "No response generated."
|
|
|
|
def categorize_and_structure_keywords(keyword_data):
|
|
categories = defaultdict(list)
|
|
|
|
for keyword_obj in keyword_data:
|
|
filtered_sources = [source for source in keyword_obj['sources'] if source['type'] != 'comment']
|
|
|
|
trimmed_sources = []
|
|
seen_titles = set()
|
|
|
|
for source in filtered_sources:
|
|
trimmed_and_lowered_title = source['text'].strip().lower()
|
|
|
|
if trimmed_and_lowered_title not in seen_titles:
|
|
trimmed_sources.append({
|
|
"text": source['text'],
|
|
"url": source['url'],
|
|
"source": source['source']
|
|
})
|
|
seen_titles.add(trimmed_and_lowered_title)
|
|
|
|
if len(trimmed_sources) == 2:
|
|
break
|
|
|
|
keyword_structure = {
|
|
"keyword": keyword_obj['keyword'],
|
|
"trending": keyword_obj['trending'],
|
|
"summary": keyword_obj['summary'],
|
|
"top_sources": trimmed_sources
|
|
}
|
|
categories[keyword_obj['category']].append(keyword_structure)
|
|
|
|
final_output = {
|
|
"categories": [
|
|
{
|
|
"category": cat,
|
|
"keywords": data
|
|
} for cat, data in categories.items()
|
|
]
|
|
}
|
|
|
|
return final_output
|
|
|
|
def generate_category_summaries(categories, time_period):
|
|
print("...generate category summaries")
|
|
|
|
for category in categories['categories']:
|
|
formatted_template = interest_category_summary_template.format(personality=personality, time_period=time_period)
|
|
|
|
combined_summaries = f"Podsumowania kategorii {category['category']}:\n\n"
|
|
|
|
for keyword in category['keywords']:
|
|
combined_summaries += keyword['summary'] + "\n\n"
|
|
|
|
combined_summaries = combined_summaries.rstrip('\n')
|
|
category_summary = generate_response_openai(combined_summaries, formatted_template)
|
|
category['category_summary'] = category_summary
|
|
|
|
return categories
|
|
|
|
def summarize_sources(data, time_period):
|
|
print("...summarize_sources")
|
|
|
|
for result in data:
|
|
|
|
if result['trending']:
|
|
formatted_template = trending_keyword_summary_template.format(personality=personality, time_period=time_period, word_count=WORD_COUNT)
|
|
else:
|
|
formatted_template = interest_keyword_summary_template.format(personality=personality, time_period=time_period, word_count=WORD_COUNT)
|
|
|
|
summary = generate_response_openai(sources_text, formatted_template)
|
|
result['summary'] = summary
|
|
|
|
return data
|
|
|
|
def build_welcome_message_with_summaries(categories_data, time_period):
|
|
print("... build welcome message")
|
|
all_summaries = "All Summaries:\n\n"
|
|
formatted_template = welcome_message_summary_template.format(personality=personality, time_period=time_period)
|
|
|
|
for category in categories_data['categories']:
|
|
all_summaries += f"Podsumowanie dla {category['category']}:\n{category['category_summary']}\n\n"
|
|
|
|
all_summaries = all_summaries.rstrip('\n')
|
|
welcome_message = generate_response_openai(all_summaries, formatted_template)
|
|
final_output = {
|
|
"categories": categories_data['categories'],
|
|
"welcome_message": welcome_message
|
|
}
|
|
|
|
return final_output
|
|
|
|
final_results = fetch_and_process_trending_items(time_period)
|
|
transformed_results = transform_and_append_sources(final_results)
|
|
|
|
|
|
keywords_only = [item['keyword'] for item in transformed_results if 'keyword' in item]
|
|
print("keywords found: ", keywords_only)
|
|
|
|
transformed_results_with_summaries = summarize_sources(transformed_results, time_period)
|
|
|
|
keywords_categories = categorize_and_structure_keywords(transformed_results_with_summaries)
|
|
keyword_categories_with_summaries = generate_category_summaries(keywords_categories, time_period)
|
|
|
|
|
|
final_object = build_welcome_message_with_summaries(keyword_categories_with_summaries, time_period)
|
|
|
|
# Sort the 'categories' list in place, moving "Keywords of Interest" to the end
|
|
final_object['categories'].sort(key=lambda x: x['category'] == "Keywords of Interest")
|
|
|
|
with open('dane.json', 'w', encoding='utf-8') as plik:
|
|
json.dump(final_object, plik, indent=4, sort_keys=True, ensure_ascii=False, separators=(',', ': '))
|
|
|