Serg4451D commited on
Commit
4dc8140
·
verified ·
1 Parent(s): e83b61c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -72
app.py CHANGED
@@ -1,118 +1,161 @@
1
  import os
 
 
2
  import gradio as gr
3
  from gradio_client import Client, handle_file
4
  from openai import OpenAI
5
 
6
- # --- Конфиг ---
7
  NV_API_KEY = os.environ.get("NV_API_KEY")
8
  if not NV_API_KEY:
9
- raise ValueError("В Secrets Hugging Face Spaces нужно задать NV_API_KEY")
10
 
11
- # Модель Florence-2
12
  florence = Client("gokaygokay/Florence-2")
13
 
14
- # Модель NVIDIA GPT-OSS-120B
15
- llm = OpenAI(
16
- base_url="https://integrate.api.nvidia.com/v1",
17
- api_key=NV_API_KEY
18
- )
19
 
20
- # --- Функции ---
21
- def get_caption(image_path):
22
- """Делаем подробную подпись через Florence-2."""
23
  try:
 
24
  result = florence.predict(
25
  image=handle_file(image_path),
26
  task_prompt="More Detailed Caption",
27
  text_input=None,
28
  model_id="microsoft/Florence-2-large",
29
- api_name="/process_image"
30
  )
 
31
  return result if isinstance(result, str) else str(result)
32
  except Exception as e:
33
  return f"[Ошибка при генерации подписи: {e}]"
34
 
35
- def chat_with_image(image_path, user_message, history):
36
- """Отправляем в LLM запрос с учетом подписи от Florence-2."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  if not image_path:
38
- return history + [[user_message, "Пожалуйста, загрузите изображение."]]
 
 
39
 
 
40
  caption = get_caption(image_path)
41
 
 
42
  system_prompt = (
43
- "Ты 'multimodal gpt-oss 120b', умный ассистент, который видит изображение.\n"
44
- f"Подробная подпись к картинке:\n{caption}\n"
45
- "Используй её, чтобы отвечать на вопросы пользователя."
 
 
46
  )
47
 
48
- history = history or []
49
  history.append([user_message, ""])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- # Стриминг ответа
52
- response_text = ""
53
- for chunk in llm.chat.completions.create(
54
- model="openai/gpt-oss-120b",
55
- messages=[
56
- {"role": "system", "content": system_prompt},
57
- {"role": "user", "content": user_message}
58
- ],
59
- temperature=0.8,
60
- top_p=1,
61
- max_tokens=1024,
62
- stream=True
63
- ):
64
- delta = chunk.choices[0].delta
65
- if delta.content:
66
- response_text += delta.content
67
- history[-1][1] = response_text
68
- yield history
69
-
70
- # --- UI ---
71
- example_images = [
72
- ["https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png"],
73
- ["https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png"],
74
- ["https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cheetah.jpg"],
75
- ["https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/flowers.png"],
76
  ]
77
 
78
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
79
- gr.Markdown(
80
- "<h1 style='text-align:center'>🖼️ multimodal gpt-oss 120b</h1>"
81
- "<p style='text-align:center'>Загружайте изображение или выберите из галереи — модель увидит его и ответит на вопросы.</p>"
82
- )
83
 
 
 
84
  with gr.Row():
85
  with gr.Column(scale=4):
86
- image_input = gr.Image(type="filepath", label="Загрузите или выберите картинку")
87
- gallery = gr.Gallery(
88
- value=example_images,
89
- label="Примеры",
90
- columns=4,
91
- height="auto",
92
- preview=True
93
- )
94
- user_input = gr.Textbox(label="Ваш вопрос", placeholder="Например: Что изображено на фото?")
95
  send_btn = gr.Button("Отправить")
 
 
 
96
 
97
  with gr.Column(scale=6):
98
- chatbot = gr.Chatbot(label="Чат", height=500)
99
- clear_btn = gr.Button("Очистить чат")
100
 
101
- # Логика выбора картинки из галереи
102
- def select_example(example):
103
- return example[0]
104
 
105
- gallery.select(select_example, inputs=[gallery], outputs=[image_input])
106
 
107
- send_btn.click(
108
- chat_with_image,
109
- inputs=[image_input, user_input, chatbot],
110
- outputs=[chatbot]
111
- )
112
 
113
- clear_btn.click(lambda: None, None, chatbot)
114
 
115
- # Запуск
116
  if __name__ == "__main__":
117
- demo.launch()
118
 
 
1
  import os
2
+ from typing import Generator, List, Tuple
3
+
4
  import gradio as gr
5
  from gradio_client import Client, handle_file
6
  from openai import OpenAI
7
 
8
+ # --- Конфигурация (в HF Spaces добавь NV_API_KEY в Secrets) ---
9
  NV_API_KEY = os.environ.get("NV_API_KEY")
10
  if not NV_API_KEY:
11
+ raise RuntimeError("Добавьте NV_API_KEY в Secrets Hugging Face Space")
12
 
13
+ # Florence-2 (публичный wrapper)
14
  florence = Client("gokaygokay/Florence-2")
15
 
16
+ # OpenAI-compatible client (NVIDIA integrate)
17
+ llm = OpenAI(base_url="https://integrate.api.nvidia.com/v1", api_key=NV_API_KEY)
18
+
 
 
19
 
20
+ def get_caption(image_path: str) -> str:
21
+ """Запрос 'More Detailed Caption' к Florence-2. image_path может быть URL или локальный путь."""
 
22
  try:
23
+ # handle_file поддерживает URL и локальные файлы
24
  result = florence.predict(
25
  image=handle_file(image_path),
26
  task_prompt="More Detailed Caption",
27
  text_input=None,
28
  model_id="microsoft/Florence-2-large",
29
+ api_name="/process_image",
30
  )
31
+ # result может быть строкой или структурой — нормализуем
32
  return result if isinstance(result, str) else str(result)
33
  except Exception as e:
34
  return f"[Ошибка при генерации подписи: {e}]"
35
 
36
+
37
+ def _extract_text_from_chunk(chunk) -> str:
38
+ """Универсальная попытка извлечь текстовый фрагмент из stream-chunk."""
39
+ try:
40
+ # объект-атрибутный стиль
41
+ if hasattr(chunk, "choices"):
42
+ choice = chunk.choices[0]
43
+ delta = getattr(choice, "delta", None)
44
+ if delta is not None:
45
+ txt = getattr(delta, "content", None) or getattr(delta, "reasoning_content", None)
46
+ return txt or ""
47
+ # dict-стиль
48
+ if isinstance(chunk, dict):
49
+ choices = chunk.get("choices", [])
50
+ if choices:
51
+ delta = choices[0].get("delta", {})
52
+ return delta.get("content") or delta.get("reasoning_content") or ""
53
+ except Exception:
54
+ return ""
55
+ return ""
56
+
57
+
58
+ def chat_stream(image_path: str, user_message: str, history: List[Tuple[str, str]]):
59
+ """
60
+ Generator для Gradio: сначала возвращает caption, затем по мере прихода токенов
61
+ обновляет последний ответ ассистента.
62
+ Возвращаемые значения — кортежи (history, caption) соответствующие outputs.
63
+ """
64
+ history = history or []
65
+
66
  if not image_path:
67
+ history.append([user_message, "Пожалуйста, загрузите изображение."])
68
+ yield history, ""
69
+ return
70
 
71
+ # Получаем подробную подпись
72
  caption = get_caption(image_path)
73
 
74
+ # Сборка системного промпта
75
  system_prompt = (
76
+ "You are 'multimodal gpt-oss 120b'. Use the provided 'More Detailed Caption' as authoritative visual context.\n\n"
77
+ "Image Caption START >>>\n"
78
+ f"{caption}\n"
79
+ "<<< Image Caption END.\n"
80
+ "When answering, mention visible details and be explicit when uncertain."
81
  )
82
 
83
+ # Добавляем сообщение пользователя
84
  history.append([user_message, ""])
85
+ # Первый yield — чтобы UI сразу показал пользовательское сообщение и подпись
86
+ yield history, caption
87
+
88
+ assistant_text = ""
89
+ try:
90
+ stream = llm.chat.completions.create(
91
+ model="openai/gpt-oss-120b",
92
+ messages=[
93
+ {"role": "system", "content": system_prompt},
94
+ {"role": "user", "content": user_message},
95
+ ],
96
+ temperature=0.8,
97
+ top_p=1.0,
98
+ max_tokens=1024,
99
+ stream=True,
100
+ )
101
 
102
+ for chunk in stream:
103
+ piece = _extract_text_from_chunk(chunk)
104
+ if not piece:
105
+ continue
106
+ assistant_text += piece
107
+ history[-1][1] = assistant_text
108
+ yield history, caption
109
+
110
+ except Exception as e:
111
+ # В случае ошибки — покажем её в чате
112
+ history[-1][1] = f"[Ошибка стриминга LLM: {e}]"
113
+ yield history, caption
114
+
115
+ # Финальный yield (гарантируем состояние завершения)
116
+ yield history, caption
117
+
118
+
119
+ # --- UI (для HF Spaces) ---
120
+ EXAMPLE_IMAGES = [
121
+ # список простых строк (URL или локальные пути). НИКАКИХ вложенных списков!
122
+ "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
123
+ "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png",
124
+ "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cheetah.jpg",
125
+ "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/flowers.png",
 
126
  ]
127
 
128
+ css = """
129
+ #title {text-align:center; margin-bottom: -18px;}
130
+ .gradio-container { max-width: 1100px; margin: auto; }
131
+ """
 
132
 
133
+ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
134
+ gr.Markdown("<h2 id='title'>🖼️ multimodal gpt-oss 120b — визуальный чат</h2>")
135
  with gr.Row():
136
  with gr.Column(scale=4):
137
+ image_input = gr.Image(label="Загрузите картинку или выберите из галереи", type="filepath", tool="editor")
138
+ raw_caption = gr.Textbox(label="More Detailed Caption (Florence-2)", interactive=False)
139
+ user_input = gr.Textbox(label="Вопрос по изображению", placeholder="Например: 'Что происходит на фото?'")
 
 
 
 
 
 
140
  send_btn = gr.Button("Отправить")
141
+ clear_btn = gr.Button("Очистить чат")
142
+ gr.Markdown("**Галерея примеров (клик — подставить в загрузчик)**")
143
+ gallery = gr.Gallery(value=EXAMPLE_IMAGES, columns=4, label="Примеры", show_label=False).style(grid=[4], height="auto")
144
 
145
  with gr.Column(scale=6):
146
+ chatbot = gr.Chatbot(label="Чат с моделью", height=600)
 
147
 
148
+ # Клик по картинке в галерее -> вставляем URL/путь в image_input
149
+ def pick_example(img_url: str):
150
+ return img_url
151
 
152
+ gallery.select(fn=pick_example, inputs=[gallery], outputs=[image_input])
153
 
154
+ # Кнопка отправки: п��ивязываем генератор, который возвращает (chat_history, caption)
155
+ send_btn.click(fn=chat_stream, inputs=[image_input, user_input, chatbot], outputs=[chatbot, raw_caption])
 
 
 
156
 
157
+ clear_btn.click(lambda: [], None, chatbot)
158
 
 
159
  if __name__ == "__main__":
160
+ demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
161