gradio_test_2 / app.py
albertakn's picture
Update app.py
e4711d7 verified
# app.py
import os
import typing as t
import gradio as gr
from openai import OpenAI, AsyncOpenAI
VLLM_BASE_URL = os.getenv("VLLM_BASE_URL")
VLLM_MODEL = os.getenv("VLLM_MODEL", "avibe")
OPENAI_API_KEY = ""
def _build_messages(
user_message: str,
history: list[tuple[str, str]] | None,
system_prompt: str | None,
) -> list[dict[str, str]]:
"""Конвертация истории gr.ChatInterface в формат OpenAI Chat API."""
messages: list[dict[str, str]] = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
if history:
for user_turn, assistant_turn in history:
if user_turn:
messages.append({"role": "user", "content": user_turn})
if assistant_turn:
messages.append({"role": "assistant", "content": assistant_turn})
messages.append({"role": "user", "content": user_message})
return messages
def _parse_stop_sequences(stop_sequences_raw: str | None) -> list[str] | None:
"""Парсит стоп-последовательности из строки (по запятым или переводам строк)."""
if not stop_sequences_raw:
return None
raw = stop_sequences_raw.replace("\r", "\n").replace("\n", ",")
items = [s.strip() for s in raw.split(",")]
result = [s for s in items if s]
return result or None
def _create_client(base_url: str | None, api_key: str | None):
"""Синхронный клиент OpenAI-совместимого API (если понадобится)."""
effective_api_key = api_key or os.environ.get("OPENAI_API_KEY") or "EMPTY_KEY"
kwargs: dict[str, t.Any] = {"api_key": effective_api_key}
if base_url:
kwargs["base_url"] = base_url
return OpenAI(**kwargs)
def _create_async_client(base_url: str | None, api_key: str | None):
"""Асинхронный клиент OpenAI-совместимого API (vLLM/SGLang/TGI совместим)."""
effective_api_key = api_key or os.environ.get("OPENAI_API_KEY") or "EMPTY_KEY"
kwargs: dict[str, t.Any] = {"api_key": effective_api_key}
if base_url:
kwargs["base_url"] = base_url
return AsyncOpenAI(**kwargs)
async def generate_response(
user_message: str,
history: list[tuple[str, str]] | None,
temperature: float,
top_p: float,
max_tokens: int | float | None,
system_prompt: str,
):
"""
Генерация ответа с потоковой выдачей через OpenAI-совместимый vLLM/SGLang/TGI.
Это async-генератор: Gradio ChatInterface будет отображать промежуточный текст
по мере поступления токенов (streaming). См. доки ChatInterface.
"""
try:
client = _create_async_client(base_url=VLLM_BASE_URL, api_key=OPENAI_API_KEY)
messages = _build_messages(
user_message=user_message,
history=history,
system_prompt=system_prompt,
)
# Безопасная обработка max_tokens
max_new_tokens: int | None = None
if isinstance(max_tokens, (int, float)) and max_tokens > 0:
max_new_tokens = int(max_tokens)
create_kwargs: dict[str, t.Any] = {
"model": VLLM_MODEL,
"messages": messages,
"temperature": float(temperature),
"top_p": float(top_p),
"max_tokens": max_new_tokens,
"stream": True, # просим потоковую выдачу
}
# Для AsyncOpenAI при stream=True возвращается асинхронный поток событий
stream = await client.chat.completions.create(**create_kwargs)
accumulated_text = ""
async for chunk in stream:
try:
# стандартный путь для openai>=1.** c Chat Completions
delta = chunk.choices[0].delta
content_piece = getattr(delta, "content", None)
if content_piece:
accumulated_text += content_piece
# возвращаем на каждом шаге наращиваемую строку
yield accumulated_text
except Exception:
# запасной путь для несовпадающих реализаций стриминга
choice0 = chunk.choices[0]
content_piece = None
if hasattr(choice0, "delta") and isinstance(choice0.delta, dict):
content_piece = choice0.delta.get("content")
elif hasattr(choice0, "message") and isinstance(choice0.message, dict):
content_piece = choice0.message.get("content")
if content_piece:
accumulated_text += content_piece
yield accumulated_text
# Если поток завершился без текста — вернём пустую строку
if not accumulated_text:
yield ""
except Exception as e:
# Показываем текст ошибки прямо в чате
yield f"Ошибка: {e}"
def build_demo() -> gr.Blocks:
"""Собираем интерфейс Gradio ChatInterface с дополнительными контролами."""
with gr.Blocks(title="vLLM Chat (Gradio)") as demo:
gr.Markdown("### Тестим версию avibe после этапа GRPO")
gr.ChatInterface(
fn=generate_response,
submit_btn="Отправить",
additional_inputs=[
gr.Slider(
minimum=0.0, maximum=2.0, value=0.7, step=0.01, label="temperature"
),
gr.Slider(
minimum=0.0, maximum=1.0, value=0.95, step=0.01, label="top_p"
),
gr.Slider(
minimum=1, maximum=8192, value=512, step=1, label="max_tokens"
),
gr.Textbox(
label="system prompt",
placeholder="Вы можете задать стиль и поведение ассистента...",
lines=3,
),
],
)
return demo
def main() -> None:
"""Точка входа. На HF Spaces НЕ указываем порт/хост и не используем share=True."""
demo = build_demo()
# demo.queue() # можно включить, если ожидается высокая конкуррентность
demo.launch(debug=True)
if __name__ == "__main__":
main()