Beexly commited on
Commit
324e371
·
verified ·
1 Parent(s): d8f6d13

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. README.md +27 -5
  2. __pycache__/app.cpython-311.pyc +0 -0
  3. app.py +24 -111
  4. requirements.txt +0 -2
README.md CHANGED
@@ -37,11 +37,33 @@ or prediction engine — this is Garrett's own tooling, unrelated to GSE.**
37
  - Streaming responses
38
  - Copy button on every reply (hover a message)
39
  - Chat history capped at the last 12 turns to bound context growth
40
- - **Documents tab**: upload PDF/DOCX/PPTX/HTML/TXT/MD, parsed locally via
41
- `docling` (MIT, no external API), chunked, and indexed with BM25 keyword
42
- search (`rank_bm25`, pure Python, no embedding model, no extra GPU
43
- weight). Check "Search my documents" in the Chat tab to have the model
44
- retrieve and cite relevant excerpts instead of answering blind.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  ## Swapping the model
47
 
 
37
  - Streaming responses
38
  - Copy button on every reply (hover a message)
39
  - Chat history capped at the last 12 turns to bound context growth
40
+
41
+ ## Tried and reverted: document-grounded chat (RAG)
42
+
43
+ A Documents tab (upload PDF/DOCX/PPTX, parsed via `docling`, retrieved with
44
+ BM25) was built and pushed, then reverted. Real cause, not a code bug: this
45
+ Space's own `MODEL_ID` (72B) downloads **145GB** of raw weights before
46
+ 4-bit quantization, against a **150GB per-Space storage cap** — docling's
47
+ own supplementary models pushed it over, and the container was evicted
48
+ ("Workload evicted, storage limit exceeded (150G)"). There isn't enough
49
+ storage headroom left on this Space for anything else meaningful. If
50
+ document-grounded chat is wanted later, it needs either a smaller base
51
+ model here, or to live on a Space that isn't already this close to the
52
+ storage ceiling (e.g. `studio-media`, whose models total well under half
53
+ that footprint).
54
+
55
+ ## Fixed: a real gradio_client bug
56
+
57
+ ZeroGPU force-pins `gradio[oauth]==5.9.1`. Its bundled `gradio_client`
58
+ (1.5.2) has a real bug: its JSON-schema parser assumes every schema node is
59
+ a `dict`, but valid JSON Schema allows a bare boolean (e.g.
60
+ `additionalProperties: true`) — this crashed *every* API call to this
61
+ Space with `TypeError: argument of type 'bool' is not iterable`. The
62
+ earlier fix (pinning `pydantic==2.10.3`, which happens to avoid generating
63
+ that schema shape) was a workaround, not a fix, and it broke `docling`'s
64
+ own serialization elsewhere. The real fix, now in `app.py`: a small
65
+ monkeypatch on `gradio_client.utils._json_schema_to_python_type` that
66
+ handles non-dict schema nodes directly. No pydantic pin needed.
67
 
68
  ## Swapping the model
69
 
__pycache__/app.cpython-311.pyc CHANGED
Binary files a/__pycache__/app.cpython-311.pyc and b/__pycache__/app.cpython-311.pyc differ
 
app.py CHANGED
@@ -9,17 +9,17 @@ from transformers import (
9
  BitsAndBytesConfig,
10
  TextIteratorStreamer,
11
  )
12
- from docling.document_converter import DocumentConverter
13
- from rank_bm25 import BM25Okapi
14
 
15
  # ---------------------------------------------------------------------------
16
  # Fix a real bug in gradio_client 1.5.2 (bundled with ZeroGPU's forced
17
  # gradio==5.9.1): its JSON-schema-to-python-type parser assumes every schema
18
  # node is a dict, but valid JSON Schema allows a bare boolean (e.g.
19
  # additionalProperties: true) — that crashes EVERY API call to this Space
20
- # with "TypeError: argument of type 'bool' is not iterable". Patched here,
21
- # not worked around with an old pydantic pin (which broke docling's own
22
- # serialization) — this is the actual bug.
 
 
23
  import gradio_client.utils as _gc_utils
24
 
25
  _orig_json_schema_to_python_type = _gc_utils._json_schema_to_python_type
@@ -35,8 +35,6 @@ _gc_utils._json_schema_to_python_type = _patched_json_schema_to_python_type
35
 
36
  MODEL_ID = "Qwen/Qwen2.5-72B-Instruct"
37
  MAX_HISTORY_TURNS = 12
38
- CHUNK_CHARS = 800
39
- TOP_K = 4
40
 
41
  SYSTEM_PROMPTS = {
42
  "General": "You are a helpful assistant.",
@@ -68,82 +66,16 @@ model = AutoModelForCausalLM.from_pretrained(
68
  attn_implementation="sdpa",
69
  )
70
 
71
- _docling_converter = None
72
- CHUNKS = [] # list of {"source": filename, "text": chunk_text}
73
- BM25_INDEX = None
74
-
75
-
76
- def _docling():
77
- global _docling_converter
78
- if _docling_converter is None:
79
- _docling_converter = DocumentConverter()
80
- return _docling_converter
81
-
82
-
83
- def _chunk_markdown(md_text, source, max_chars=CHUNK_CHARS):
84
- paragraphs = [p.strip() for p in md_text.split("\n\n") if p.strip()]
85
- chunks, current = [], ""
86
- for p in paragraphs:
87
- if len(current) + len(p) + 1 <= max_chars:
88
- current = f"{current}\n{p}".strip()
89
- else:
90
- if current:
91
- chunks.append(current)
92
- current = p
93
- if current:
94
- chunks.append(current)
95
- return [{"source": source, "text": c} for c in chunks]
96
-
97
-
98
- def index_documents(files):
99
- global CHUNKS, BM25_INDEX
100
- if not files:
101
- return "No files given.", ""
102
- new_chunks = []
103
- for f in files:
104
- result = _docling().convert(f.name if hasattr(f, "name") else f)
105
- md = result.document.export_to_markdown()
106
- source = (f.name if hasattr(f, "name") else str(f)).split("/")[-1]
107
- new_chunks.extend(_chunk_markdown(md, source))
108
- CHUNKS = new_chunks # replace — one working set at a time, kept simple
109
- tokenized = [c["text"].lower().split() for c in CHUNKS]
110
- BM25_INDEX = BM25Okapi(tokenized) if tokenized else None
111
- sources = sorted({c["source"] for c in CHUNKS})
112
- status = f"Indexed {len(sources)} document(s), {len(CHUNKS)} chunks."
113
- listing = "\n".join(f"- {s}" for s in sources)
114
- return status, listing
115
-
116
-
117
- def _retrieve(query, k=TOP_K):
118
- if BM25_INDEX is None or not CHUNKS:
119
- return []
120
- scores = BM25_INDEX.get_scores(query.lower().split())
121
- ranked = sorted(range(len(CHUNKS)), key=lambda i: scores[i], reverse=True)
122
- return [CHUNKS[i] for i in ranked[:k] if scores[i] > 0]
123
-
124
-
125
- def _duration(message, history, system_prompt_name, use_docs, max_new_tokens, temperature):
126
  return min(120, 40 + int(max_new_tokens) // 10)
127
 
128
 
129
  @spaces.GPU(duration=_duration)
130
- def generate(message, history, system_prompt_name, use_docs, max_new_tokens, temperature):
131
  history = history[-(MAX_HISTORY_TURNS * 2):]
132
  system_prompt = SYSTEM_PROMPTS.get(system_prompt_name, SYSTEM_PROMPTS["General"])
133
 
134
- retrieved = _retrieve(message) if use_docs else []
135
- if retrieved:
136
- context_block = "\n\n".join(
137
- f"[Source: {c['source']}]\n{c['text']}" for c in retrieved
138
- )
139
- system_prompt = (
140
- f"{system_prompt}\n\nUse the following excerpts from the user's own "
141
- "uploaded documents if relevant to their question. Cite the source "
142
- "filename in brackets when you use one. If the excerpts don't answer "
143
- "the question, say so and answer from general knowledge instead.\n\n"
144
- f"{context_block}"
145
- )
146
-
147
  messages = [{"role": "system", "content": system_prompt}]
148
  for turn in history:
149
  messages.append({"role": turn["role"], "content": turn["content"]})
@@ -181,42 +113,23 @@ def generate(message, history, system_prompt_name, use_docs, max_new_tokens, tem
181
 
182
  chatbot = gr.Chatbot(type="messages", show_copy_button=True, label="Chat")
183
 
184
- with gr.Blocks(title="Beexly Studio Chat (ZeroGPU)") as demo:
185
- gr.Markdown(
186
- "# Beexly Studio Chat (ZeroGPU)\n"
 
 
 
 
 
 
 
 
 
 
187
  f"Personal free-GPU chat — `{MODEL_ID}` (4-bit), streaming, on ZeroGPU. "
188
- "Not part of any Galaxy Sports Edge product."
189
- )
190
- with gr.Tab("Chat"):
191
- chat_iface = gr.ChatInterface(
192
- fn=generate,
193
- type="messages",
194
- chatbot=chatbot,
195
- additional_inputs=[
196
- gr.Dropdown(
197
- list(SYSTEM_PROMPTS.keys()), value="General", label="System prompt"
198
- ),
199
- gr.Checkbox(
200
- label="Search my documents (Documents tab)", value=False
201
- ),
202
- gr.Slider(64, 1024, value=512, step=64, label="Max new tokens"),
203
- gr.Slider(0.0, 1.5, value=0.7, step=0.1, label="Temperature"),
204
- ],
205
- )
206
- with gr.Tab("Documents"):
207
- gr.Markdown(
208
- "Upload documents (PDF/DOCX/PPTX/HTML/TXT/MD), parsed locally via "
209
- "`docling` (MIT, no external API) and retrieved with BM25 keyword "
210
- "search — no embedding model, no extra GPU weight. Check "
211
- "'Search my documents' in the Chat tab to use them."
212
- )
213
- docs_files = gr.File(label="Documents", file_count="multiple")
214
- docs_btn = gr.Button("Index documents")
215
- docs_status = gr.Textbox(label="Status", interactive=False)
216
- docs_list = gr.Textbox(label="Indexed sources", interactive=False)
217
- docs_btn.click(
218
- index_documents, inputs=docs_files, outputs=[docs_status, docs_list]
219
- )
220
 
221
  if __name__ == "__main__":
222
  demo.launch()
 
9
  BitsAndBytesConfig,
10
  TextIteratorStreamer,
11
  )
 
 
12
 
13
  # ---------------------------------------------------------------------------
14
  # Fix a real bug in gradio_client 1.5.2 (bundled with ZeroGPU's forced
15
  # gradio==5.9.1): its JSON-schema-to-python-type parser assumes every schema
16
  # node is a dict, but valid JSON Schema allows a bare boolean (e.g.
17
  # additionalProperties: true) — that crashes EVERY API call to this Space
18
+ # with "TypeError: argument of type 'bool' is not iterable". Patched here
19
+ # rather than pinning an old pydantic (which broke docling's own
20
+ # serialization when this Space briefly tried to carry a Documents/RAG tab —
21
+ # reverted, see README: the 72B model's 145GB raw download leaves no real
22
+ # room in the 150GB per-Space storage cap for anything else).
23
  import gradio_client.utils as _gc_utils
24
 
25
  _orig_json_schema_to_python_type = _gc_utils._json_schema_to_python_type
 
35
 
36
  MODEL_ID = "Qwen/Qwen2.5-72B-Instruct"
37
  MAX_HISTORY_TURNS = 12
 
 
38
 
39
  SYSTEM_PROMPTS = {
40
  "General": "You are a helpful assistant.",
 
66
  attn_implementation="sdpa",
67
  )
68
 
69
+
70
+ def _duration(message, history, system_prompt_name, max_new_tokens, temperature):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  return min(120, 40 + int(max_new_tokens) // 10)
72
 
73
 
74
  @spaces.GPU(duration=_duration)
75
+ def generate(message, history, system_prompt_name, max_new_tokens, temperature):
76
  history = history[-(MAX_HISTORY_TURNS * 2):]
77
  system_prompt = SYSTEM_PROMPTS.get(system_prompt_name, SYSTEM_PROMPTS["General"])
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  messages = [{"role": "system", "content": system_prompt}]
80
  for turn in history:
81
  messages.append({"role": turn["role"], "content": turn["content"]})
 
113
 
114
  chatbot = gr.Chatbot(type="messages", show_copy_button=True, label="Chat")
115
 
116
+ demo = gr.ChatInterface(
117
+ fn=generate,
118
+ type="messages",
119
+ chatbot=chatbot,
120
+ additional_inputs=[
121
+ gr.Dropdown(
122
+ list(SYSTEM_PROMPTS.keys()), value="General", label="System prompt"
123
+ ),
124
+ gr.Slider(64, 1024, value=512, step=64, label="Max new tokens"),
125
+ gr.Slider(0.0, 1.5, value=0.7, step=0.1, label="Temperature"),
126
+ ],
127
+ title="Beexly Studio Chat (ZeroGPU)",
128
+ description=(
129
  f"Personal free-GPU chat — `{MODEL_ID}` (4-bit), streaming, on ZeroGPU. "
130
+ "Hover any reply for a copy button. Not part of any Galaxy Sports Edge product."
131
+ ),
132
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
  if __name__ == "__main__":
135
  demo.launch()
requirements.txt CHANGED
@@ -4,5 +4,3 @@ transformers>=4.46.0
4
  accelerate>=1.1.0
5
  torch>=2.4.0
6
  bitsandbytes>=0.44.0
7
- docling
8
- rank_bm25
 
4
  accelerate>=1.1.0
5
  torch>=2.4.0
6
  bitsandbytes>=0.44.0