ai / main.py
doyeqkl's picture
Update main.py
d2c5d19 verified
import os
import json
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from llama_cpp import Llama
from huggingface_hub import hf_hub_download
from tavily import TavilyClient
app = FastAPI(title="Qwen Turbo Search API")
# --- КОНФИГУРАЦИЯ ---
TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
REPO_ID = "bartowski/Qwen2.5-1.5B-Instruct-GGUF"
FILENAME = "Qwen2.5-1.5B-Instruct-Q6_K.gguf"
llm = None
tavily_client = None
# --- ИНИЦИАЛИЗАЦИЯ ---
@app.on_event("startup")
def startup_event():
global llm, tavily_client
if TAVILY_API_KEY:
tavily_client = TavilyClient(api_key=TAVILY_API_KEY)
print("✅ Tavily Search подключен")
else:
print("⚠️ Нет TAVILY_API_KEY. Поиск работать не будет.")
print("🚀 Загрузка модели...")
try:
model_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME, cache_dir="./models")
llm = Llama(
model_path=model_path,
n_ctx=8192,
n_threads=2,
n_batch=1024,
verbose=False
)
print("✅ Модель готова!")
except Exception as e:
print(f"❌ Ошибка: {e}")
# --- ПОДКЛЮЧАЕМ ИНТЕРФЕЙС ---
# Создай папку static рядом с main.py!
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/")
def read_root():
# Отдаем наш HTML файл при входе на главную
return FileResponse('static/index.html')
# --- ЛОГИКА ПОИСКА ---
def perform_search(query: str):
if not tavily_client: return "Нет ключа Tavily.", []
print(f"🔎 Ищу: {query}")
try:
res = tavily_client.search(query=query, search_depth="advanced", max_results=5)
text = ""
sources = []
for i, r in enumerate(res['results']):
idx = i + 1
text += f"ИСТОЧНИК [{idx}]: {r['title']}\nТЕКСТ: {r['content']}\n\n"
sources.append({"id": idx, "title": r['title'], "url": r['url']})
return text, sources
except Exception as e:
print(f"Err: {e}")
return "Ошибка поиска.", []
# --- API ---
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
messages: list[Message]
temperature: float = 0.6
max_tokens: int = 2048
stream: bool = True
use_search: bool = False
@app.post("/v1/chat/completions")
def chat_completions(req: ChatRequest):
if not llm: raise HTTPException(503, "Loading...")
msgs = [{"role": m.role, "content": m.content} for m in req.messages]
# Поиск
if req.use_search:
query = msgs[-1]['content']
context, sources = perform_search(query)
sys_prompt = (
"Ты умный помощник. Отвечай на вопрос, используя ТОЛЬКО эти данные из интернета.\n"
"Обязательно указывай источники [1], [2].\n"
f"=== ДАННЫЕ ===\n{context}"
)
# Добавляем источники в конец последнего сообщения (для UI)
sources_md = "\n\n**Источники:**\n" + "\n".join([f"{s['id']}. [{s['title']}]({s['url']})" for s in sources])
# Инъекция системного промпта
msgs.insert(0, {"role": "system", "content": sys_prompt})
else:
sources_md = ""
# Генерация
def iter_response():
stream = llm.create_chat_completion(
messages=msgs,
temperature=req.temperature,
max_tokens=req.max_tokens,
stream=True
)
for chunk in stream:
yield f"data: {json.dumps(chunk)}\n\n"
# Если были источники, отправим их отдельным чанком в конце
if sources_md:
final_chunk = {
"choices": [{"delta": {"content": sources_md}, "finish_reason": None}]
}
yield f"data: {json.dumps(final_chunk)}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(iter_response(), media_type="text/event-stream")