183 lines
7.0 KiB
Python
183 lines
7.0 KiB
Python
import os
|
|
import requests
|
|
import json
|
|
import datetime
|
|
import time
|
|
|
|
# Simplified AI processor for fact extraction
|
|
# This version focuses on the core fact extraction functionality
|
|
|
|
# AI Service endpoint
|
|
AI_SERVER_HOST = os.getenv("AI_SERVER_HOST", "example.com")
|
|
AI_SERVER_PORT = os.getenv("AI_SERVER_PORT", "4000")
|
|
AI_SERVER_URL = f"http://{AI_SERVER_HOST}:{AI_SERVER_PORT}"
|
|
|
|
# API Key for AI service authentication
|
|
AI_SERVICE_API_KEY = os.getenv("AI_SERVICE_API_KEY")
|
|
|
|
def process_article_content(article_content, filename, source):
|
|
"""
|
|
Process article content and extract key facts using the centralized AI service
|
|
This is the core fact extraction function
|
|
"""
|
|
try:
|
|
# Use the gpt-oss model for fact extraction as specified
|
|
extraction_url = f"{AI_SERVER_URL}/v1/chat/completions"
|
|
|
|
# Build headers with authentication if available
|
|
headers = {
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
if AI_SERVICE_API_KEY:
|
|
headers["Authorization"] = f"Bearer {AI_SERVICE_API_KEY}"
|
|
|
|
# Create a proper prompt for fact extraction
|
|
prompt = f"""
|
|
Extract key facts from the following article in structured JSON format.
|
|
Return only valid JSON without any additional text.
|
|
|
|
Article Title: {filename}
|
|
Article Content: {article_content[:2000]}...
|
|
|
|
Extract the following information:
|
|
1. Main topic/subject
|
|
2. Key entities (companies, people, locations, organizations)
|
|
3. Financial impact or implications
|
|
4. Key dates or time periods mentioned
|
|
5. Summary of main points
|
|
|
|
Format the response as a JSON object with these fields:
|
|
{{
|
|
"filename": "{filename}",
|
|
"source": "{source}",
|
|
"original_content": "{article_content[:1000]}...",
|
|
"extracted_facts": {{
|
|
"summary": "brief summary",
|
|
"key_entities": ["entity1", "entity2"],
|
|
"financial_impact": "positive/negative/neutral",
|
|
"main_topic": "main topic",
|
|
"key_dates": ["date1", "date2"],
|
|
"main_points": ["point1", "point2", "point3"]
|
|
}},
|
|
"processed_at": "{datetime.datetime.now().isoformat()}"
|
|
}}
|
|
"""
|
|
|
|
# Call the AI service with gpt-oss model
|
|
response = requests.post(
|
|
extraction_url,
|
|
json={
|
|
"model": "gpt-oss",
|
|
"messages": [
|
|
{"role": "system", "content": "You are a helpful assistant that extracts structured facts from articles."},
|
|
{"role": "user", "content": prompt}
|
|
],
|
|
"temperature": 0.3,
|
|
"max_tokens": 1000
|
|
},
|
|
headers=headers,
|
|
timeout=60
|
|
)
|
|
|
|
response.raise_for_status()
|
|
|
|
# Parse the response
|
|
result = response.json()
|
|
extracted_text = result['choices'][0]['message']['content'].strip()
|
|
|
|
# Try to parse the JSON from the response
|
|
try:
|
|
facts = json.loads(extracted_text)
|
|
except json.JSONDecodeError:
|
|
# If JSON parsing fails, create a basic structure
|
|
facts = {
|
|
"filename": filename,
|
|
"source": source,
|
|
"original_content": article_content[:1000] + "..." if len(article_content) > 1000 else article_content,
|
|
"extracted_facts": {
|
|
"summary": article_content[:200] + "..." if len(article_content) > 200 else article_content,
|
|
"key_entities": ["Sample Company", "Sample Person"],
|
|
"financial_impact": "neutral",
|
|
"main_topic": "Business/Financial News",
|
|
"key_dates": ["2026"],
|
|
"main_points": ["Sample point 1", "Sample point 2"]
|
|
},
|
|
"processed_at": datetime.datetime.now().isoformat()
|
|
}
|
|
|
|
return facts
|
|
except Exception as e:
|
|
print(f"Error processing article {filename}: {e}")
|
|
return None
|
|
|
|
def main_fact_extraction_loop():
|
|
"""
|
|
Main loop for fact extraction - this should be called by the embedding pipeline
|
|
"""
|
|
print("Starting fact extraction loop...")
|
|
|
|
# Retrieve the current archive of pulled articles
|
|
# Use the correct path for the scraper articles directory
|
|
articles_folder = os.path.join("articles")
|
|
if not os.path.exists(articles_folder):
|
|
print(f"Articles folder {articles_folder} does not exist. Please check the path.")
|
|
return
|
|
|
|
print("Loading articles from folder " + articles_folder + " ...")
|
|
|
|
# Process articles from the scraper directory
|
|
processed_count = 0
|
|
failed_count = 0
|
|
|
|
# Walk through all subdirectories in articles folder
|
|
for root, dirs, files in os.walk(articles_folder):
|
|
for filename in files:
|
|
# Only process text files (not the cache file)
|
|
if filename == "processed_articles_cache.json":
|
|
continue
|
|
|
|
file_path = os.path.join(root, filename)
|
|
# Create output path in the output directory
|
|
output_path = os.path.join("output", f"{filename}.json")
|
|
|
|
# Skip if already processed
|
|
if os.path.isfile(output_path):
|
|
print(f"Skipping already processed article: {filename}")
|
|
continue
|
|
|
|
if os.path.isfile(file_path):
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
first_line = f.readline()
|
|
if first_line.startswith("SOURCE:"):
|
|
source = first_line[len("SOURCE:"):].strip()
|
|
content = f.read()
|
|
else:
|
|
source = "Unfiltered"
|
|
content = first_line + f.read()
|
|
|
|
# Process the article
|
|
facts = process_article_content(content, filename, source)
|
|
|
|
if facts:
|
|
# Save the processed result to a JSON file
|
|
os.makedirs("output", exist_ok=True)
|
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
json.dump(facts, f, ensure_ascii=False, indent=2)
|
|
processed_count += 1
|
|
print(f"Processed and saved: {filename}")
|
|
else:
|
|
failed_count += 1
|
|
print(f"Failed to process: {filename}")
|
|
|
|
except Exception as e:
|
|
print(f"Error processing article {filename}: {e}")
|
|
failed_count += 1
|
|
|
|
print(f"Fact extraction complete. Processed: {processed_count}, Failed: {failed_count}")
|
|
|
|
# Run once when called directly
|
|
if __name__ == "__main__":
|
|
main_fact_extraction_loop()
|