|
|
import gradio as gr |
|
|
import os |
|
|
import requests |
|
|
import json |
|
|
import time |
|
|
from dotenv import load_dotenv |
|
|
|
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
def create_deepseek_interface(): |
|
|
|
|
|
api_key = os.getenv("FW_API_KEY") |
|
|
serphouse_api_key = os.getenv("SERPHOUSE_API_KEY") |
|
|
|
|
|
if not api_key: |
|
|
print("경고: FW_API_KEY 환경 변수가 설정되지 않았습니다.") |
|
|
if not serphouse_api_key: |
|
|
print("경고: SERPHOUSE_API_KEY 환경 변수가 설정되지 않았습니다.") |
|
|
|
|
|
|
|
|
def extract_keywords_with_llm(query): |
|
|
if not api_key: |
|
|
return "LLM 키워드 추출을 위한 FW_API_KEY가 설정되지 않았습니다.", query |
|
|
|
|
|
|
|
|
url = "https://api.fireworks.ai/inference/v1/chat/completions" |
|
|
payload = { |
|
|
"model": "accounts/fireworks/models/deepseek-v3-0324", |
|
|
"max_tokens": 200, |
|
|
"temperature": 0.1, |
|
|
"messages": [ |
|
|
{ |
|
|
"role": "system", |
|
|
"content": "사용자의 질문에서 웹 검색에 효과적인 핵심 키워드를 추출하세요. 키워드 사이에 쉼표나 공백을 넣지 말고 하나의 검색어처럼 수정해서 제공하세요. 예를 들어 '한덕수 국무총리 탄핵 결과'처럼 공백으로만 구분하세요." |
|
|
}, |
|
|
{ |
|
|
"role": "user", |
|
|
"content": query |
|
|
} |
|
|
] |
|
|
} |
|
|
headers = { |
|
|
"Accept": "application/json", |
|
|
"Content-Type": "application/json", |
|
|
"Authorization": f"Bearer {api_key}" |
|
|
} |
|
|
|
|
|
try: |
|
|
response = requests.post(url, headers=headers, json=payload) |
|
|
response.raise_for_status() |
|
|
result = response.json() |
|
|
|
|
|
|
|
|
keywords = result["choices"][0]["message"]["content"].strip() |
|
|
|
|
|
|
|
|
if len(keywords) > 100: |
|
|
return f"추출된 키워드: {keywords}", query |
|
|
|
|
|
return f"추출된 키워드: {keywords}", keywords |
|
|
|
|
|
except Exception as e: |
|
|
print(f"키워드 추출 중 오류 발생: {str(e)}") |
|
|
return f"키워드 추출 중 오류 발생: {str(e)}", query |
|
|
|
|
|
|
|
|
def search_with_serphouse(query): |
|
|
if not serphouse_api_key: |
|
|
return "SERPHOUSE_API_KEY가 설정되지 않았습니다." |
|
|
|
|
|
try: |
|
|
|
|
|
extraction_result, search_query = extract_keywords_with_llm(query) |
|
|
print(f"원본 쿼리: {query}") |
|
|
print(extraction_result) |
|
|
|
|
|
|
|
|
url = "https://api.serphouse.com/serp/live" |
|
|
|
|
|
|
|
|
is_korean = any('\uAC00' <= c <= '\uD7A3' for c in search_query) |
|
|
|
|
|
|
|
|
params = { |
|
|
"q": search_query, |
|
|
"domain": "google.com", |
|
|
"serp_type": "web", |
|
|
"device": "desktop", |
|
|
"lang": "ko" if is_korean else "en" |
|
|
} |
|
|
|
|
|
headers = { |
|
|
"Authorization": f"Bearer {serphouse_api_key}" |
|
|
} |
|
|
|
|
|
print(f"SerpHouse API 호출 중... 기본 GET 방식으로 시도") |
|
|
print(f"검색어: {search_query}") |
|
|
print(f"요청 URL: {url} - 파라미터: {params}") |
|
|
|
|
|
|
|
|
response = requests.get(url, headers=headers, params=params) |
|
|
response.raise_for_status() |
|
|
|
|
|
print(f"SerpHouse API 응답 상태 코드: {response.status_code}") |
|
|
search_results = response.json() |
|
|
|
|
|
|
|
|
print(f"응답 구조: {list(search_results.keys()) if isinstance(search_results, dict) else '딕셔너리 아님'}") |
|
|
|
|
|
|
|
|
formatted_results = [] |
|
|
formatted_results.append(f"## 검색어: {search_query}\n\n") |
|
|
|
|
|
|
|
|
organic_results = None |
|
|
|
|
|
|
|
|
if "results" in search_results and "organic" in search_results["results"]: |
|
|
organic_results = search_results["results"]["organic"] |
|
|
|
|
|
|
|
|
elif "organic" in search_results: |
|
|
organic_results = search_results["organic"] |
|
|
|
|
|
|
|
|
elif "results" in search_results and "results" in search_results["results"]: |
|
|
if "organic" in search_results["results"]["results"]: |
|
|
organic_results = search_results["results"]["results"]["organic"] |
|
|
|
|
|
|
|
|
if organic_results and len(organic_results) > 0: |
|
|
|
|
|
print(f"첫번째 organic 결과 구조: {organic_results[0].keys() if len(organic_results) > 0 else 'empty'}") |
|
|
|
|
|
for i, result in enumerate(organic_results[:5], 1): |
|
|
title = result.get("title", "제목 없음") |
|
|
snippet = result.get("snippet", "내용 없음") |
|
|
link = result.get("link", "#") |
|
|
displayed_link = result.get("displayed_link", link) |
|
|
|
|
|
|
|
|
formatted_results.append( |
|
|
f"### {i}. [{title}]({link})\n\n" |
|
|
f"{snippet}\n\n" |
|
|
f"**출처**: [{displayed_link}]({link})\n\n" |
|
|
f"---\n\n" |
|
|
) |
|
|
|
|
|
print(f"검색 결과 {len(organic_results)}개 찾음") |
|
|
return "".join(formatted_results) |
|
|
|
|
|
|
|
|
print("검색 결과 없음 또는 예상치 못한 응답 구조") |
|
|
print(f"응답 구조 상세: {search_results.keys() if hasattr(search_results, 'keys') else '불명확한 구조'}") |
|
|
|
|
|
|
|
|
error_msg = "검색 결과가 없거나 응답 형식이 예상과 다릅니다" |
|
|
if "error" in search_results: |
|
|
error_msg = search_results["error"] |
|
|
elif "message" in search_results: |
|
|
error_msg = search_results["message"] |
|
|
|
|
|
return f"## 검색어 '{search_query}'에 대한 결과\n\n{error_msg}" |
|
|
|
|
|
except Exception as e: |
|
|
error_msg = f"검색 중 오류 발생: {str(e)}" |
|
|
print(error_msg) |
|
|
import traceback |
|
|
print(traceback.format_exc()) |
|
|
|
|
|
|
|
|
return f"## 오류 발생\n\n" + \ |
|
|
f"검색 중 오류가 발생했습니다: **{str(e)}**\n\n" + \ |
|
|
f"### API 요청 상세 정보:\n" + \ |
|
|
f"- **URL**: {url}\n" + \ |
|
|
f"- **검색어**: {search_query}\n" + \ |
|
|
f"- **파라미터**: {params}\n" |
|
|
|
|
|
|
|
|
def query_deepseek_streaming(message, history, use_deep_research): |
|
|
if not api_key: |
|
|
yield history, "환경 변수 FW_API_KEY가 설정되지 않았습니다. 서버에서 환경 변수를 확인해주세요." |
|
|
return |
|
|
|
|
|
search_context = "" |
|
|
search_info = "" |
|
|
if use_deep_research: |
|
|
try: |
|
|
|
|
|
yield history + [(message, "🔍 최적의 키워드 추출 및 웹 검색 중...")], "" |
|
|
|
|
|
|
|
|
print(f"Deep Research 활성화됨: 메시지 '{message}'에 대한 검색 시작") |
|
|
search_results = search_with_serphouse(message) |
|
|
print(f"검색 결과 수신 완료: {search_results[:100]}...") |
|
|
|
|
|
if not search_results.startswith("검색 중 오류 발생") and not search_results.startswith("SERPHOUSE_API_KEY"): |
|
|
search_context = f""" |
|
|
다음은 사용자 질문과 관련된 최신 검색 결과입니다. 이 정보를 참고하여 정확하고 최신 정보가 반영된 응답을 제공하세요: |
|
|
|
|
|
{search_results} |
|
|
|
|
|
위 검색 결과를 기반으로 사용자의 다음 질문에 답변하세요. 검색 결과에서 명확한 답변을 찾을 수 없는 경우, 당신의 지식을 활용하여 최선의 답변을 제공하세요. |
|
|
검색 결과를 인용할 때는 출처를 명시하고, 답변이 최신 정보를 반영하도록 하세요. |
|
|
""" |
|
|
search_info = f"🔍 Deep Research 기능 활성화: 관련 웹 검색 결과를 기반으로 응답 생성 중..." |
|
|
else: |
|
|
print(f"검색 실패 또는 결과 없음: {search_results}") |
|
|
except Exception as e: |
|
|
print(f"Deep Research 처리 중 예외 발생: {str(e)}") |
|
|
search_info = f"🔍 Deep Research 기능 오류: {str(e)}" |
|
|
|
|
|
|
|
|
messages = [] |
|
|
for user, assistant in history: |
|
|
messages.append({"role": "user", "content": user}) |
|
|
messages.append({"role": "assistant", "content": assistant}) |
|
|
|
|
|
|
|
|
if search_context: |
|
|
|
|
|
messages.insert(0, {"role": "system", "content": search_context}) |
|
|
|
|
|
|
|
|
messages.append({"role": "user", "content": message}) |
|
|
|
|
|
|
|
|
url = "https://api.fireworks.ai/inference/v1/chat/completions" |
|
|
payload = { |
|
|
"model": "accounts/fireworks/models/deepseek-v3-0324", |
|
|
"max_tokens": 20480, |
|
|
"top_p": 1, |
|
|
"top_k": 40, |
|
|
"presence_penalty": 0, |
|
|
"frequency_penalty": 0, |
|
|
"temperature": 0.6, |
|
|
"messages": messages, |
|
|
"stream": True |
|
|
} |
|
|
headers = { |
|
|
"Accept": "application/json", |
|
|
"Content-Type": "application/json", |
|
|
"Authorization": f"Bearer {api_key}" |
|
|
} |
|
|
|
|
|
try: |
|
|
|
|
|
response = requests.request("POST", url, headers=headers, data=json.dumps(payload), stream=True) |
|
|
response.raise_for_status() |
|
|
|
|
|
|
|
|
new_history = history.copy() |
|
|
|
|
|
|
|
|
start_msg = search_info if search_info else "" |
|
|
new_history.append((message, start_msg)) |
|
|
|
|
|
|
|
|
full_response = start_msg |
|
|
|
|
|
|
|
|
for line in response.iter_lines(): |
|
|
if line: |
|
|
line_text = line.decode('utf-8') |
|
|
|
|
|
|
|
|
if line_text.startswith("data: "): |
|
|
line_text = line_text[6:] |
|
|
|
|
|
|
|
|
if line_text == "[DONE]": |
|
|
break |
|
|
|
|
|
try: |
|
|
|
|
|
chunk = json.loads(line_text) |
|
|
chunk_content = chunk.get("choices", [{}])[0].get("delta", {}).get("content", "") |
|
|
|
|
|
if chunk_content: |
|
|
full_response += chunk_content |
|
|
|
|
|
new_history[-1] = (message, full_response) |
|
|
yield new_history, "" |
|
|
except json.JSONDecodeError: |
|
|
continue |
|
|
|
|
|
|
|
|
yield new_history, "" |
|
|
|
|
|
except requests.exceptions.RequestException as e: |
|
|
error_msg = f"API 오류: {str(e)}" |
|
|
if hasattr(e, 'response') and e.response and e.response.status_code == 401: |
|
|
error_msg = "인증 실패. 환경 변수 FW_API_KEY를 확인해주세요." |
|
|
yield history, error_msg |
|
|
|
|
|
|
|
|
with gr.Blocks(theme="soft", fill_height=True) as demo: |
|
|
|
|
|
gr.Markdown( |
|
|
""" |
|
|
# 🤖 DeepSeek V3-0324 + Reserach |
|
|
### DeepSeek V3-0324 최신 모델 + 실시간 'Deep Research' Agentic AI 시스템 @ https://discord.gg/openfreeai |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(): |
|
|
|
|
|
chatbot = gr.Chatbot( |
|
|
height=500, |
|
|
show_label=False, |
|
|
container=True |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=3): |
|
|
use_deep_research = gr.Checkbox( |
|
|
label="Deep Research 활성화", |
|
|
info="최적의 키워드 추출 및 웹 검색을 통한 최신 정보 활용", |
|
|
value=False |
|
|
) |
|
|
with gr.Column(scale=1): |
|
|
api_status = gr.Markdown("API 상태: 준비됨") |
|
|
|
|
|
|
|
|
if not serphouse_api_key: |
|
|
api_status.value = "⚠️ SERPHOUSE_API_KEY가 설정되지 않았습니다" |
|
|
if not api_key: |
|
|
api_status.value = "⚠️ FW_API_KEY가 설정되지 않았습니다" |
|
|
if api_key and serphouse_api_key: |
|
|
api_status.value = "✅ API 키 설정 완료" |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
msg = gr.Textbox( |
|
|
label="메시지", |
|
|
placeholder="여기에 프롬프트를 입력하세요...", |
|
|
show_label=False, |
|
|
scale=9 |
|
|
) |
|
|
submit = gr.Button("전송", variant="primary", scale=1) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
clear = gr.ClearButton([msg, chatbot], value="🧹 대화 초기화") |
|
|
|
|
|
|
|
|
gr.Examples( |
|
|
examples=[ |
|
|
"딥러닝에서 트랜스포머와 RNN의 차이점을 설명해주세요.", |
|
|
"특정 범위 내의 소수를 찾는 파이썬 함수를 작성해주세요.", |
|
|
"강화학습의 주요 개념을 요약해주세요." |
|
|
], |
|
|
inputs=msg |
|
|
) |
|
|
|
|
|
|
|
|
error_box = gr.Markdown("") |
|
|
|
|
|
|
|
|
submit.click( |
|
|
query_deepseek_streaming, |
|
|
inputs=[msg, chatbot, use_deep_research], |
|
|
outputs=[chatbot, error_box] |
|
|
).then( |
|
|
lambda: "", |
|
|
None, |
|
|
[msg] |
|
|
) |
|
|
|
|
|
|
|
|
msg.submit( |
|
|
query_deepseek_streaming, |
|
|
inputs=[msg, chatbot, use_deep_research], |
|
|
outputs=[chatbot, error_box] |
|
|
).then( |
|
|
lambda: "", |
|
|
None, |
|
|
[msg] |
|
|
) |
|
|
|
|
|
return demo |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo = create_deepseek_interface() |
|
|
demo.launch(debug=True) |