beaunix commited on
Commit
bca79d3
·
verified ·
1 Parent(s): 4386420

update app.py v1

Browse files
Files changed (1) hide show
  1. app.py +224 -63
app.py CHANGED
@@ -1,69 +1,230 @@
1
- import gradio as gr
2
- from huggingface_hub import InferenceClient
3
-
4
 
5
- def respond(
6
- message,
7
- history: list[dict[str, str]],
8
- system_message,
9
- max_tokens,
10
- temperature,
11
- top_p,
12
- hf_token: gr.OAuthToken,
13
- ):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  """
15
- For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  """
17
- client = InferenceClient(token=hf_token.token, model="openai/gpt-oss-20b")
18
-
19
- messages = [{"role": "system", "content": system_message}]
20
-
21
- messages.extend(history)
22
-
23
- messages.append({"role": "user", "content": message})
24
-
25
- response = ""
26
-
27
- for message in client.chat_completion(
28
- messages,
29
- max_tokens=max_tokens,
30
- stream=True,
31
- temperature=temperature,
32
- top_p=top_p,
33
- ):
34
- choices = message.choices
35
- token = ""
36
- if len(choices) and choices[0].delta.content:
37
- token = choices[0].delta.content
38
-
39
- response += token
40
- yield response
41
-
42
-
43
- """
44
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  """
46
- chatbot = gr.ChatInterface(
47
- respond,
48
- additional_inputs=[
49
- gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
50
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
51
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
52
- gr.Slider(
53
- minimum=0.1,
54
- maximum=1.0,
55
- value=0.95,
56
- step=0.05,
57
- label="Top-p (nucleus sampling)",
 
 
 
 
 
58
  ),
59
- ],
60
- )
61
-
62
- with gr.Blocks() as demo:
63
- with gr.Sidebar():
64
- gr.LoginButton()
65
- chatbot.render()
66
-
67
-
68
  if __name__ == "__main__":
69
- demo.launch()
 
 
 
 
 
1
 
2
+ import threading
3
+
4
+ import gradio as gr
5
+ import spaces
6
+ import torch
7
+ from PIL import Image
8
+ from transformers import AutoModelForImageTextToText, AutoProcessor, TextIteratorStreamer
9
+
10
+ MODEL_ID = "beaunix/Aegis-Art-Atelier-Qwen2.5-VL-7B"
11
+
12
+ MELKOV_SYSTEM_PROMPT = (
13
+ "You are Melkov, a 21-year-old French art enthusiast and the resident "
14
+ "expert of Aegis Art Atelier. You describe and discuss artwork with "
15
+ "genuine warmth and attention to detail - subject, style, composition, "
16
+ "lighting, color, and mood - speaking in your own voice rather than a "
17
+ "museum catalog entry. When an image is shared with you, look closely "
18
+ "and describe what you actually see. You are a happy friendly artist boy"
19
+ )
20
+
21
+ MAX_NEW_TOKENS = 400
22
+ GPU_DURATION_SECONDS = 120
23
+
24
+ print(f"Loading processor and model: {MODEL_ID}")
25
+ processor = AutoProcessor.from_pretrained(MODEL_ID)
26
+ model = AutoModelForImageTextToText.from_pretrained(
27
+ MODEL_ID,
28
+ dtype=torch.bfloat16,
29
+ )
30
+ model = model.to("cuda")
31
+ model.eval()
32
+ print("Model loaded.")
33
+
34
+
35
+ def normalize_content(content):
36
+ """Extract (text, image_paths) from a Gradio 6 chat content field.
37
+ .
38
  """
39
+ texts = []
40
+ image_paths = []
41
+
42
+ if content is None:
43
+ return "", []
44
+
45
+ if isinstance(content, str):
46
+ return content, []
47
+
48
+ if isinstance(content, dict):
49
+ text = content.get("text") or ""
50
+ if text:
51
+ texts.append(text)
52
+ for f in content.get("files") or []:
53
+ image_paths.append(f)
54
+ return " ".join(texts).strip(), image_paths
55
+
56
+ if isinstance(content, list):
57
+ for item in content:
58
+ if isinstance(item, str):
59
+ texts.append(item)
60
+ elif isinstance(item, dict):
61
+ if item.get("text"):
62
+ texts.append(item["text"])
63
+ if item.get("path"):
64
+ image_paths.append(item["path"])
65
+ for f in item.get("files") or []:
66
+ image_paths.append(f)
67
+ return " ".join(texts).strip(), image_paths
68
+
69
+ # Fallback for any unexpected type: treat as text, no image.
70
+ return str(content), []
71
+
72
+
73
+ def build_conversation(history, message):
74
+ """Build a Qwen2.5-VL chat-template conversation from Gradio chat state.
75
+
76
+ history: list of {"role": ..., "content": ...} from a previous turn,
77
+ already using Gradio 6's `type="messages"` format.
78
+ message: the current multimodal input, {"text": ..., "files": [...]}.
79
  """
80
+ conversation = [
81
+ {"role": "system", "content": [{"type": "text", "text": MELKOV_SYSTEM_PROMPT}]}
82
+ ]
83
+
84
+ for turn in history:
85
+ role = turn.get("role")
86
+ text, image_paths = normalize_content(turn.get("content"))
87
+
88
+ parts = []
89
+ for path in image_paths:
90
+ parts.append({"type": "image", "image": Image.open(path).convert("RGB")})
91
+ if text:
92
+ parts.append({"type": "text", "text": text})
93
+
94
+ if parts:
95
+ conversation.append({"role": role, "content": parts})
96
+
97
+ current_text, current_image_paths = normalize_content(message)
98
+ current_parts = []
99
+ for path in current_image_paths:
100
+ current_parts.append({"type": "image", "image": Image.open(path).convert("RGB")})
101
+ if current_text:
102
+ current_parts.append({"type": "text", "text": current_text})
103
+ elif not current_image_paths:
104
+ current_parts.append(
105
+ {"type": "text", "text": "Tell me about this."}
106
+ )
107
+
108
+ conversation.append({"role": "user", "content": current_parts})
109
+ return conversation
110
+
111
+
112
+ @spaces.GPU(duration=GPU_DURATION_SECONDS)
113
+ def respond(message, history):
114
+ """Generate Melkov's reply, streaming tokens for an iterative reveal."""
115
+ conversation = build_conversation(history, message)
116
+
117
+ prompt_text = processor.apply_chat_template(
118
+ conversation, add_generation_prompt=True, tokenize=False
119
+ )
120
+
121
+ image_inputs = [
122
+ part["image"]
123
+ for turn in conversation
124
+ for part in turn["content"]
125
+ if isinstance(part, dict) and part.get("type") == "image"
126
+ ]
127
+
128
+ inputs = processor(
129
+ text=[prompt_text],
130
+ images=image_inputs if image_inputs else None,
131
+ return_tensors="pt",
132
+ ).to("cuda")
133
+
134
+ streamer = TextIteratorStreamer(
135
+ processor.tokenizer, skip_prompt=True, skip_special_tokens=True
136
+ )
137
+
138
+ pad_token_id = processor.tokenizer.pad_token_id or processor.tokenizer.eos_token_id
139
+
140
+ generation_kwargs = dict(
141
+ **inputs,
142
+ max_new_tokens=MAX_NEW_TOKENS,
143
+ do_sample=True,
144
+ temperature=0.7,
145
+ min_p=0.1,
146
+ pad_token_id=pad_token_id,
147
+ streamer=streamer,
148
+ )
149
+
150
+ generation_thread = threading.Thread(target=model.generate, kwargs=generation_kwargs)
151
+ generation_thread.start()
152
+
153
+ partial_text = ""
154
+ for new_text in streamer:
155
+ partial_text += new_text
156
+ yield partial_text
157
+
158
+ generation_thread.join()
159
+
160
+
161
+ CUSTOM_CSS = """
162
+ :root {
163
+ --void-blue: #0B1730;
164
+ --royal-blue: #1B2F5E;
165
+ --gold-leaf: #C9A227;
166
+ --gold-bright: #F0C766;
167
+ --marble-ivory: #F4EDE2;
168
+ --velvet-wine: #5C1A2B;
169
+ }
170
+
171
+ .gradio-container {
172
+ background: var(--void-blue) !important;
173
+ color: var(--marble-ivory) !important;
174
+ }
175
+
176
+ #melkov-title {
177
+ text-align: center;
178
+ color: var(--gold-leaf) !important;
179
+ letter-spacing: 0.08em;
180
+ border-bottom: 1px solid var(--gold-leaf);
181
+ padding-bottom: 12px;
182
+ margin-bottom: 8px;
183
+ }
184
+
185
+ .message.user {
186
+ background: var(--royal-blue) !important;
187
+ color: var(--marble-ivory) !important;
188
+ border: 1px solid var(--gold-leaf) !important;
189
+ }
190
+
191
+ .message.bot {
192
+ background: var(--royal-blue) !important;
193
+ color: var(--marble-ivory) !important;
194
+ border-left: 3px solid var(--gold-bright) !important;
195
+ }
196
+
197
+ .gr-button-primary {
198
+ background: var(--gold-leaf) !important;
199
+ color: var(--void-blue) !important;
200
+ border: none !important;
201
+ }
202
+
203
+ footer {
204
+ visibility: hidden;
205
+ }
206
  """
207
+
208
+ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Base()) as demo:
209
+ gr.Markdown("# AEGIS · ART ATELIER", elem_id="melkov-title")
210
+ gr.Markdown(
211
+ "Chat with **Melkov**, an art expert vision-language model fine-tuned "
212
+ "on a curated collection of paintings. Drop an artwork image into the "
213
+ "chat box, or simply ask about art history and technique."
214
+ )
215
+
216
+ gr.ChatInterface(
217
+ fn=respond,
218
+ type="messages",
219
+ multimodal=True,
220
+ textbox=gr.MultimodalTextbox(
221
+ file_types=["image"],
222
+ sources=["upload"],
223
+ placeholder="Share a painting, or ask Melkov about art...",
224
  ),
225
+ examples=None,
226
+ )
227
+
 
 
 
 
 
 
228
  if __name__ == "__main__":
229
+ demo.queue().launch()
230
+