Spaces:
Running
Running
import gradio as gr | |
import json | |
import os | |
from typing import List | |
from concurrent.futures import ThreadPoolExecutor, as_completed | |
from openai import OpenAI | |
import itertools | |
import ast | |
# API Keys | |
OPENAI_API_KEY = os.getenv("YOUR_OPENAI_API_KEY") | |
openai_client = OpenAI(api_key=OPENAI_API_KEY) | |
openai_tools = [{"type": "web_search_preview", "search_context_size": "high", "user_location": {"type": "approximate","country": "UA","city": "Kiev"}}] | |
# JSON parser | |
def parse_json_response(response: str) -> list: | |
cleaned = response.replace("json", "").replace("```", "").replace("\n", "") | |
print(cleaned) | |
return json.loads(cleaned) | |
def query_openai(prompt: str) -> str: | |
try: | |
response = openai_client.responses.create( | |
model="gpt-4.1", | |
temperature=0.5, | |
max_output_tokens=10000, | |
tools=openai_tools, | |
input=prompt, | |
) | |
return response.output_text | |
except Exception as e: | |
return f"❌ Error: {e}" | |
# Parallel LLM execution | |
def run_parallel(prompts: List[str], query_fn, max_workers: int = 5) -> List[str]: | |
results = [] | |
with ThreadPoolExecutor(max_workers=max_workers) as executor: | |
futures = {executor.submit(query_fn, prompt): prompt for prompt in prompts} | |
for future in as_completed(futures): | |
try: | |
res = parse_json_response(future.result()) | |
results.append(res) | |
except Exception as e: | |
print(e) | |
pass | |
return results | |
#Parallel LLM execution | |
def run_parallel_wo_validation(prompts: List[str], query_fn, max_workers: int = 5) -> List[str]: | |
results = [] | |
with ThreadPoolExecutor(max_workers=max_workers) as executor: | |
futures = {executor.submit(query_fn, prompt): prompt for prompt in prompts} | |
for future in as_completed(futures): | |
results.append(future.result()) | |
return results | |
# Prompt builders | |
JSON_INSTRUCTION = 'Поверни list з структурою ["name", "name", ..., "name", "name"]. Поверни тільки цей list. Без будь-якого іншого тексту' | |
def build_company_prompt(query: str) -> str: | |
return f"""Твоя задача надати мені список топ 10 компаній, які пов`язані з (входять в) {query}. | |
Надай список із 10 успішних компаній які відносяться до {query}. | |
Шукай все що напряму пов'язано з {query}""" + JSON_INSTRUCTION | |
def build_people_prompt(area: str) -> str: | |
return f"""Твоя задача надати мені список топ 5 успішних людей, які пов`язані з {area}. | |
Надай список 5 успішних людей (ім`я та прізвище). | |
Шукай все що напряму пов'язано з {area} (компанії, дочірні компанії, співробітники, і тд)""" + JSON_INSTRUCTION | |
def build_books_prompt(name: str, query: str) -> str: | |
return f"""Ти найкращий пошуковий помічник по книгам у світі. Надай мені список книг, які рекомендував {name}. | |
Мене цікавлять тільки назви книг та автори, не пиши нічого іншого. В відповіді повинні бути лише назва книги. | |
Проаналізуй всі статті, публікації, згадки у соціальних мережах про {name} і знайди саме книги які ця людина рекомендувала. | |
Ці книги потрібні щоб віповісти на запит користувача рекомендації книг від {name} зі сфери {query}. | |
Якщо ти нічого не зміг знайти поверни "". БІЛЬШЕ НІЧОГО НЕ ПИШИ.""" | |
# Generator function for progressive output | |
def on_click(query, context): | |
query = f"{query} {context}".strip() if context else query.strip() | |
log = "" | |
log += "🔍 Шукаю пов`язані бізнеси...\n" | |
yield log.strip(), gr.update(visible=False) | |
company_responses = run_parallel([build_company_prompt(query)] * 3, query_openai) | |
company_responses = list(set([item for sublist in company_responses for item in sublist])) | |
log += "👤 Шукаю найуспішніших людей у цій сфері...\n" | |
yield log.strip(), gr.update(visible=False) | |
company_responses.append(query) | |
print(company_responses) | |
people_prompts = [build_people_prompt(f"{k} from {query}") for k in list(company_responses)] | |
people_responses = run_parallel(people_prompts, query_openai) | |
people_responses = list(set([item for sublist in people_responses for item in sublist])) | |
print(people_responses) | |
people = people_responses | |
log += f"✅ Знайдено людей: {', '.join(people)}\n" | |
log += "📚 Збираю рекомендації книжок...\n" | |
yield log.strip(), gr.update(visible=False) | |
book_prompts = [build_books_prompt(name, query) for name in people] | |
book_results = run_parallel_wo_validation(book_prompts, query_openai, max_workers=10) | |
print(book_results) | |
all_raw_text = " ".join(filter(lambda x: x not in (None, '', '""', "''"), book_results)) | |
#log += all_raw_text | |
extract_prompt = f"Твоя задача дістати із всього тексту, який я тобі надішлю, всі книги та написати їх автора. Поверни лише назву книги та авторів. Сформуй список із всіх цих книг разом з автором та поверни його. Видали дублікати, якщо вони є. Весь текст: {all_raw_text}. Поверни лише новий список з авторами і нічого більше." | |
final_response = query_openai(extract_prompt) | |
books = final_response.strip().split("\n") | |
def format_books_for_textbox(book_list): | |
# Filter out empty entries and clean the text | |
cleaned_books = [book.strip() for book in book_list if book.strip()] | |
# Remove duplicates while preserving order | |
seen = set() | |
unique_books = [] | |
for book in cleaned_books: | |
if book.lower() not in seen: | |
seen.add(book.lower()) | |
unique_books.append(book) | |
# Format with proper line breaks and bullet points | |
if not unique_books: | |
return "На жаль, не вдалося знайти рекомендовані книги для цієї сфери." | |
return "\n\n".join(f"📖 {book}" for book in unique_books) | |
log += "✅ Готово! Книги, рекомендовані експертами у цій сфері:" | |
yield log.strip(), gr.update(value=format_books_for_textbox(books), visible=True) | |
# Gradio interface | |
with gr.Blocks(title="📚 BookRecommender") as demo: | |
gr.Markdown(""" | |
# 📚 BookRecommender | |
Введи сферу або компанію, і ми зберемо книжки, які рекомендують найуспішніші люди в цій галузі. | |
""") | |
query_input = gr.Textbox(label="Назва сфери або компанії", placeholder="Наприклад: Universe Group, Genesis, IT-компанії України") | |
context_input = gr.Textbox(label="Контекст", placeholder="Наприклад: в Україні, на світовому ринку") | |
search_button = gr.Button("🔍 Знайти книжки") | |
status_box = gr.Textbox(label="Прогрес виконання", interactive=False) | |
book_output = gr.Textbox(label="Список рекомендованих книжок", lines=20, interactive=True, visible=False) | |
search_button.click( | |
fn=on_click, | |
inputs=[query_input, context_input], | |
outputs=[status_box, book_output] | |
) | |
if __name__ == "__main__": | |
demo.launch() |