File size: 8,257 Bytes
4699d61
 
38af1b1
3e78d5c
4699d61
 
24e4972
1795c94
4699d61
c8156cc
c556a1e
4699d61
799d8f9
d9259e5
c8156cc
4699d61
 
d0fefeb
4699d61
 
59d7eb8
4699d61
 
 
 
 
 
 
 
 
 
 
 
 
c8156cc
4699d61
 
 
 
 
fd61500
1991fd4
d4403c2
2682bf9
 
fd61500
 
4699d61
 
45d0d6b
 
 
 
 
 
a1f2bf6
45d0d6b
 
c8156cc
2e87939
4699d61
 
4fc00a2
fd61500
10837a2
4699d61
 
326bfe4
fd61500
10837a2
4699d61
d0fefeb
45d0d6b
 
 
a1f2bf6
10837a2
 
2e34aea
d9f7a6d
 
9754a79
59d7eb8
 
2e34aea
e76c1af
fd61500
 
22c1da4
2a8b254
59d7eb8
74016ec
e76c1af
 
 
fd61500
 
 
 
22c1da4
e76c1af
4699d61
59d7eb8
 
 
2e34aea
e76c1af
bee28e6
45d0d6b
1795c94
 
4699d61
 
a3d3af1
8a0fcb8
4699d61
59d7eb8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8156cc
 
59d7eb8
 
 
 
2e34aea
 
59d7eb8
0da7b5b
59d7eb8
 
 
 
9754a79
 
d9f7a6d
59d7eb8
9754a79
3e78d5c
2e34aea
5907d8c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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()