| import gradio as gr |
| import pandas as pd |
| import numpy as np |
| from langchain_openai import OpenAIEmbeddings, ChatOpenAI |
| from langchain.text_splitter import RecursiveCharacterTextSplitter |
| from langchain_community.vectorstores import FAISS |
| from langchain.prompts import ChatPromptTemplate |
| from langchain.schema.runnable import RunnablePassthrough |
| from langchain.schema.output_parser import StrOutputParser |
| import os |
| from dotenv import load_dotenv |
|
|
| |
| load_dotenv() |
| OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") |
|
|
| |
| df = pd.read_csv("K-Funding250331.csv") |
|
|
| |
| def clean_and_prepare_data(df): |
| |
| df_clean = df.fillna("") |
| |
| |
| documents = [] |
| for idx, row in df_clean.iterrows(): |
| |
| doc_content = f"Startup: {row['Startup Name']}\n" |
| for col in df_clean.columns: |
| if col != 'Startup Name' and row[col]: |
| doc_content += f"{col}: {row[col]}\n" |
| |
| documents.append({"content": doc_content, "source": f"row_{idx}"}) |
| |
| return documents |
|
|
| |
| documents = clean_and_prepare_data(df) |
|
|
| |
| text_splitter = RecursiveCharacterTextSplitter( |
| chunk_size=1000, |
| chunk_overlap=100 |
| ) |
| splits = text_splitter.create_documents([doc["content"] for doc in documents]) |
|
|
| |
| for i, split in enumerate(splits): |
| split.metadata = {"source": f"chunk_{i}"} |
|
|
| |
| embeddings = OpenAIEmbeddings() |
| vectorstore = FAISS.from_documents(splits, embeddings) |
| retriever = vectorstore.as_retriever(search_kwargs={"k": 5}) |
|
|
| |
| def translate_entity_names(query): |
| |
| translation_model = ChatOpenAI(model="gpt-4o-mini", temperature=0) |
| |
| |
| translation_prompt = ChatPromptTemplate.from_messages([ |
| ("system", """You are a Korean-English translator specializing in Korean startup and investor names. |
| Extract any potential company or investor names from the user's query and provide their Korean translations. |
| |
| If you detect company or investor names, return them in this format: |
| "TRANSLATION_RESULT: {{English Name}} -> {{Korean Name}} (Type: company/investor)" |
| |
| You can return multiple translations if multiple entities are detected: |
| "TRANSLATION_RESULT: |
| {{English Name 1}} -> {{Korean Name 1}} (Type: company) |
| {{English Name 2}} -> {{Korean Name 2}} (Type: investor)" |
| |
| If no relevant names are detected, return: |
| "TRANSLATION_RESULT: NO_ENTITIES_DETECTED" |
| |
| Do not provide any explanations or additional text, just the translation result."""), |
| ("human", "{query}") |
| ]) |
| |
| translation_chain = translation_prompt | translation_model | StrOutputParser() |
| result = translation_chain.invoke({"query": query}) |
| |
| |
| translations = [] |
| modified_query = query |
| |
| if "TRANSLATION_RESULT:" in result: |
| result_parts = result.strip().split("TRANSLATION_RESULT:")[1].strip() |
| |
| if result_parts != "NO_ENTITIES_DETECTED": |
| |
| translation_lines = [line.strip() for line in result_parts.split('\n') if line.strip()] |
| |
| for line in translation_lines: |
| if "->" in line: |
| |
| name_part, type_part = line.rsplit("(Type:", 1) if "(Type:" in line else (line, "(Type: unknown)") |
| english_name, korean_name = name_part.split("->") |
| entity_type = type_part.replace(")", "").strip() |
| |
| translation = { |
| "english_name": english_name.strip(), |
| "korean_name": korean_name.strip(), |
| "entity_type": entity_type.strip() |
| } |
| translations.append(translation) |
| |
| |
| modified_query = modified_query.replace(english_name.strip(), korean_name.strip()) |
| |
| if translations: |
| return { |
| "translated": True, |
| "translations": translations, |
| "modified_query": modified_query |
| } |
| else: |
| return { |
| "translated": False, |
| "original_query": query |
| } |
|
|
| |
| system_template = """You are an expert financial analyst specializing in Korean startup investments. |
| Answer the user's question based on the following retrieved information. |
| If you don't know the answer, say that you don't know. |
| Use the retrieved information to provide insights, summaries, and analysis. |
| |
| Retrieved information: |
| {context} |
| |
| Additional instructions: |
| - Translate any Korean text to English in your response |
| - If comparing multiple startups or sectors, create a structured comparison |
| - If analyzing trends, provide clear patterns and insights |
| - Be precise with numbers and statistics |
| - If information is missing, acknowledge it rather than making assumptions |
| - When referencing any company name, use both the English translated name and also the original Korean name in bracket |
| - Do the same for investor names - provide both English and Korean names where possible |
| """ |
|
|
| |
| human_template = "{question}" |
|
|
| |
| chat_prompt = ChatPromptTemplate.from_messages([ |
| ("system", system_template), |
| ("human", human_template), |
| ]) |
|
|
| |
| model = ChatOpenAI(model="gpt-4o-mini", temperature=0) |
|
|
| |
| def retrieve_and_generate(query_info): |
| |
| query = query_info["question"] |
| context = query_info.get("context", "") |
| |
| |
| result = chat_prompt.invoke({ |
| "context": context, |
| "question": query |
| }) |
| |
| return model.invoke(result) |
|
|
| |
| def handle_query(query): |
| |
| translation_result = translate_entity_names(query) |
| |
| |
| if translation_result.get("translated", False): |
| original_query = query |
| query_for_retrieval = translation_result["modified_query"] |
| |
| |
| translation_notes = [] |
| for t in translation_result["translations"]: |
| translation_notes.append(f"'{t['english_name']}' to '{t['korean_name']}' (Type: {t['entity_type']})") |
| |
| translation_info = ", ".join(translation_notes) |
| enhanced_query = f"{original_query}\n\nNote: I've translated: {translation_info} for better retrieval." |
| else: |
| query_for_retrieval = query |
| enhanced_query = query |
| |
| |
| if any(word in query.lower() for word in ["all", "overall", "summary", "statistics", "stats"]): |
| |
| summary_stats = generate_statistics(df) |
| |
| |
| context = f"Overall Dataset Statistics:\n{summary_stats}\n\n" |
| |
| |
| retrieved_docs = retriever.invoke(query_for_retrieval) |
| retrieved_context = "\n\n".join([doc.page_content for doc in retrieved_docs]) |
| |
| combined_context = context + retrieved_context |
| |
| |
| response = retrieve_and_generate({ |
| "context": combined_context, |
| "question": enhanced_query |
| }) |
| |
| return StrOutputParser().invoke(response) |
| |
| |
| retrieved_docs = retriever.invoke(query_for_retrieval) |
| retrieved_context = "\n\n".join([doc.page_content for doc in retrieved_docs]) |
| |
| |
| response = retrieve_and_generate({ |
| "context": retrieved_context, |
| "question": enhanced_query |
| }) |
| |
| return StrOutputParser().invoke(response) |
|
|
| |
| def generate_statistics(df): |
| stats = [] |
| |
| |
| stats.append(f"Total startups: {len(df)}") |
| |
| |
| sectors = df["Sector"].dropna().unique() |
| stats.append(f"Unique sectors: {len(sectors)}") |
| |
| |
| if "Round (KR)" in df.columns: |
| round_counts = df["Round (KR)"].value_counts().to_dict() |
| stats.append("Funding round distribution:") |
| for round_type, count in round_counts.items(): |
| stats.append(f" - {round_type}: {count}") |
| |
| |
| if "Investment Amount" in df.columns: |
| |
| stats.append("Investment statistics available in the dataset") |
| |
| return "\n".join(stats) |
|
|
| |
| demo = gr.Interface( |
| fn=handle_query, |
| inputs=gr.Textbox(label="Ask a question about Korean startup investments"), |
| outputs=gr.Textbox(label="AI Answer"), |
| title="Korean Startup Knowledge Base", |
| description="Ask questions about Korean startups, funding trends, sectors, or investment rounds.", |
| examples=[ |
| ["Which investors are most active in the healthcare sector?"], |
| ["What was the funding trend for AI startups in 2025 so far?"], |
| ["What do you know about Spoon Labs?"], |
| ["Tell me about investments made by Kakao Investment."] |
| ] |
| ) |
|
|
| |
| if __name__ == "__main__": |
| demo.launch() |