Shubadecka commited on
Commit
678b831
·
1 Parent(s): c2ff08b

better image handling

Browse files
agent/__pycache__/__init__.cpython-310.pyc CHANGED
Binary files a/agent/__pycache__/__init__.cpython-310.pyc and b/agent/__pycache__/__init__.cpython-310.pyc differ
 
agent/__pycache__/orchestrator.cpython-310.pyc CHANGED
Binary files a/agent/__pycache__/orchestrator.cpython-310.pyc and b/agent/__pycache__/orchestrator.cpython-310.pyc differ
 
agent/__pycache__/tools.cpython-310.pyc CHANGED
Binary files a/agent/__pycache__/tools.cpython-310.pyc and b/agent/__pycache__/tools.cpython-310.pyc differ
 
app.py CHANGED
@@ -1,24 +1,10 @@
1
  import base64
2
- from pathlib import Path
3
- from typing import Any
4
 
5
  import gradio as gr
6
  from huggingface_hub import InferenceClient
7
  from PIL import Image
8
 
9
-
10
- def _image_to_data_url(image_path: str, max_side: int = 1120, quality: int = 85) -> str:
11
- """Resize and encode a local image file as a base64 JPEG data URL."""
12
- with Image.open(image_path) as img:
13
- img = img.convert("RGB")
14
- w, h = img.size
15
- if max(w, h) > max_side:
16
- scale = max_side / max(w, h)
17
- img = img.resize((int(w * scale), int(h * scale)), Image.LANCZOS)
18
- buf = io.BytesIO()
19
- img.save(buf, format="JPEG", quality=quality)
20
- return "data:image/jpeg;base64," + base64.b64encode(buf.getvalue()).decode()
21
-
22
  from agent import run_agent
23
 
24
  MODEL = "Qwen/Qwen3-VL-30B-A3B-Thinking"
@@ -33,72 +19,51 @@ DEFAULT_SYSTEM = (
33
 
34
 
35
  def _image_to_data_url(image_path: str, max_side: int = 1120, quality: int = 85) -> str:
36
- """
37
- Convert a local image file to a base64 data URL, resizing it first so the
38
- payload stays within the HF router's request-size limit.
39
-
40
- Images are downscaled so their longest side is at most `max_side` pixels,
41
- then saved as JPEG at `quality` to keep the base64 size small.
42
- """
43
- from PIL import Image
44
- import io
45
-
46
  with Image.open(image_path) as img:
47
  img = img.convert("RGB")
48
  w, h = img.size
49
  if max(w, h) > max_side:
50
  scale = max_side / max(w, h)
51
  img = img.resize((int(w * scale), int(h * scale)), Image.LANCZOS)
52
-
53
  buf = io.BytesIO()
54
  img.save(buf, format="JPEG", quality=quality)
55
- data = base64.b64encode(buf.getvalue()).decode("utf-8")
56
-
57
- return f"data:image/jpeg;base64,{data}"
58
-
59
-
60
- def _build_user_content(text: str, image_path: str | None) -> Any:
61
- """
62
- Build the `content` field for a user message.
63
-
64
- Returns a plain string when there is no image, or a list of content parts
65
- (text + image_url) when an image is present.
66
- """
67
- if not image_path:
68
- return text or ""
69
-
70
- parts: list[dict] = []
71
- if text:
72
- parts.append({"type": "text", "text": text})
73
- parts.append(
74
- {
75
- "type": "image_url",
76
- "image_url": {"url": _image_to_data_url(image_path)},
77
- }
78
- )
79
- return parts
80
 
81
 
82
  def respond(
83
- message: str,
84
  history: list[dict],
85
- image: str | None,
86
- system_message: str,
87
- max_tokens: int,
88
- temperature: float,
89
- top_p: float,
90
  hf_token: gr.OAuthToken,
91
  ):
92
  client = InferenceClient(token=hf_token.token, model=MODEL)
93
 
94
- # Build the full message list: system + history + new user turn
95
- messages: list[dict] = [{"role": "system", "content": system_message}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  messages.extend(history)
 
97
 
98
- user_content = _build_user_content(message, image)
99
- messages.append({"role": "user", "content": user_content})
100
-
101
- # Run the agentic loop and get back the final answer string
102
  answer = run_agent(
103
  messages=messages,
104
  client=client,
@@ -108,36 +73,28 @@ def respond(
108
  top_p=top_p,
109
  )
110
 
111
- # Yield in chunks to preserve Gradio's streaming UX
112
- chunk_size = 8
113
  partial = ""
114
- for i in range(0, len(answer), chunk_size):
115
- partial += answer[i : i + chunk_size]
116
  yield partial
117
 
118
 
 
 
 
 
 
 
 
 
 
 
 
119
  with gr.Blocks() as demo:
120
  with gr.Sidebar():
121
  gr.LoginButton()
122
- image_input = gr.Image(label="Upload image", type="filepath")
123
-
124
- gr.ChatInterface(
125
- respond,
126
- additional_inputs=[
127
- image_input,
128
- gr.Textbox(value=DEFAULT_SYSTEM, label="System message"),
129
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
130
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
131
- gr.Slider(
132
- minimum=0.1,
133
- maximum=1.0,
134
- value=0.95,
135
- step=0.05,
136
- label="Top-p (nucleus sampling)",
137
- ),
138
- ],
139
- )
140
 
141
 
142
  if __name__ == "__main__":
143
- demo.launch()
 
1
  import base64
2
+ import io
 
3
 
4
  import gradio as gr
5
  from huggingface_hub import InferenceClient
6
  from PIL import Image
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  from agent import run_agent
9
 
10
  MODEL = "Qwen/Qwen3-VL-30B-A3B-Thinking"
 
19
 
20
 
21
  def _image_to_data_url(image_path: str, max_side: int = 1120, quality: int = 85) -> str:
22
+ """Resize and base64-encode a local image so the HF payload stays under the limit."""
 
 
 
 
 
 
 
 
 
23
  with Image.open(image_path) as img:
24
  img = img.convert("RGB")
25
  w, h = img.size
26
  if max(w, h) > max_side:
27
  scale = max_side / max(w, h)
28
  img = img.resize((int(w * scale), int(h * scale)), Image.LANCZOS)
 
29
  buf = io.BytesIO()
30
  img.save(buf, format="JPEG", quality=quality)
31
+ return "data:image/jpeg;base64," + base64.b64encode(buf.getvalue()).decode()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
 
34
  def respond(
35
+ message,
36
  history: list[dict],
37
+ system_message,
38
+ max_tokens,
39
+ temperature,
40
+ top_p,
 
41
  hf_token: gr.OAuthToken,
42
  ):
43
  client = InferenceClient(token=hf_token.token, model=MODEL)
44
 
45
+ # multimodal=True sends {"text": str, "files": [path, ...]}
46
+ # Guard against plain strings in case of edge-case history replay
47
+ if isinstance(message, dict):
48
+ text = message.get("text", "")
49
+ files = message.get("files", [])
50
+ else:
51
+ text = message or ""
52
+ files = []
53
+
54
+ if files:
55
+ content = []
56
+ if text:
57
+ content.append({"type": "text", "text": text})
58
+ for f in files:
59
+ content.append({"type": "image_url", "image_url": {"url": _image_to_data_url(f)}})
60
+ else:
61
+ content = text
62
+
63
+ messages = [{"role": "system", "content": system_message}]
64
  messages.extend(history)
65
+ messages.append({"role": "user", "content": content})
66
 
 
 
 
 
67
  answer = run_agent(
68
  messages=messages,
69
  client=client,
 
73
  top_p=top_p,
74
  )
75
 
 
 
76
  partial = ""
77
+ for i in range(0, len(answer), 8):
78
+ partial += answer[i : i + 8]
79
  yield partial
80
 
81
 
82
+ chatbot = gr.ChatInterface(
83
+ respond,
84
+ multimodal=True,
85
+ additional_inputs=[
86
+ gr.Textbox(value=DEFAULT_SYSTEM, label="System message"),
87
+ gr.Slider(minimum=1, maximum=16384, value=8192, step=1, label="Max new tokens"),
88
+ gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
89
+ gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Top-p (nucleus sampling)"),
90
+ ],
91
+ )
92
+
93
  with gr.Blocks() as demo:
94
  with gr.Sidebar():
95
  gr.LoginButton()
96
+ chatbot.render()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
 
99
  if __name__ == "__main__":
100
+ demo.launch(ssr_mode=False)