fomext commited on
Commit
0183322
Β·
verified Β·
1 Parent(s): 1744207

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -14
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import json
 
2
  import time
3
  import uuid
4
  from typing import Optional
@@ -78,56 +79,165 @@ def list_models() -> str:
78
  return json.dumps(result)
79
 
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  def chat_completions(
82
  messages_json: str,
83
  max_tokens: int = 512,
84
  temperature: float = 0.7,
85
  top_p: float = 0.9,
 
86
  ) -> str:
87
  """
88
  Non-streaming chat completions. Returns an OpenAI-compatible JSON string.
89
 
90
- messages_json: JSON array of {role, content} objects,
91
- e.g. '[{"role":"user","content":"Hello"}]'
92
 
93
- NOTE: Qwen3-Coder-30B-A3B-Instruct is non-thinking only.
94
- The enable_thinking parameter has been removed accordingly.
95
  """
96
  try:
97
  messages = json.loads(messages_json)
98
  except json.JSONDecodeError as e:
99
  return json.dumps({"error": f"Invalid messages_json: {e}"})
100
 
 
 
 
 
 
 
 
101
  try:
102
- hf_messages = [{"role": m["role"], "content": m["content"]} for m in messages]
103
- prompt = tokenizer.apply_chat_template(
104
- hf_messages,
105
- tokenize=False,
106
- add_generation_prompt=True,
107
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  except Exception as e:
109
  return json.dumps({"error": f"Prompt build failed: {e}"})
110
 
111
  gen_kwargs = dict(
112
  max_new_tokens=max_tokens,
113
- temperature=temperature,
114
  top_p=top_p,
115
  do_sample=True,
116
  pad_token_id=tokenizer.eos_token_id,
117
  )
118
 
119
  try:
120
- content = _generate_response(prompt, gen_kwargs)
121
  except Exception as e:
122
  return json.dumps({"error": f"Generation failed: {e}"})
123
 
124
  cid = f"chatcmpl-{uuid.uuid4().hex}"
 
 
 
 
 
 
 
 
 
125
  result = {
126
  "id": cid,
127
  "object": "chat.completion",
128
  "created": int(time.time()),
129
  "model": MODEL_ALIAS,
130
- "choices": [{"index": 0, "message": {"role": "assistant", "content": content}, "finish_reason": "stop"}],
131
  "usage": {"prompt_tokens": -1, "completion_tokens": -1, "total_tokens": -1},
132
  }
133
  return json.dumps(result)
@@ -179,11 +289,12 @@ You can also chat directly below.
179
  _cc_max_tokens = gr.Number(label="max_tokens", value=512)
180
  _cc_temp = gr.Number(label="temperature", value=0.7)
181
  _cc_top_p = gr.Number(label="top_p", value=0.9)
 
182
  _cc_out = gr.Textbox(label="result")
183
  _cc_btn = gr.Button("chat_completions")
184
  _cc_btn.click(
185
  fn=chat_completions,
186
- inputs=[_cc_messages, _cc_max_tokens, _cc_temp, _cc_top_p],
187
  outputs=[_cc_out],
188
  api_name="chat_completions",
189
  )
 
1
  import json
2
+ import re
3
  import time
4
  import uuid
5
  from typing import Optional
 
79
  return json.dumps(result)
80
 
81
 
82
+ # ---------------------------------------------------------------------------
83
+ # Tool call parsing (Hermes-style: <tool_call>{...}</tool_call>)
84
+ # ---------------------------------------------------------------------------
85
+
86
+ def _parse_tool_calls(text: str):
87
+ """
88
+ Detect and extract tool calls from model output.
89
+ Handles two formats:
90
+
91
+ Format A β€” Hermes JSON (14b, 30b):
92
+ <tool_call>{"name": "fn", "arguments": {...}}</tool_call>
93
+
94
+ Format B β€” XML parameters (Qwen3-Coder):
95
+ <tool_call>
96
+ <function=fn_name>
97
+ <parameter=param1>value1</parameter>
98
+ </function>
99
+ </tool_call>
100
+
101
+ Returns (tool_calls, remaining_content) in OpenAI format,
102
+ or (None, text) if no tool calls found.
103
+ """
104
+ pattern = r'<tool_call>(.*?)</tool_call>'
105
+ matches = re.findall(pattern, text, re.DOTALL)
106
+ if not matches:
107
+ return None, text
108
+
109
+ tool_calls = []
110
+ for match in matches:
111
+ stripped = match.strip()
112
+
113
+ # ── Format A: JSON inside tool_call ──────────────────────────
114
+ try:
115
+ call = json.loads(stripped)
116
+ tool_calls.append({
117
+ "id": f"call_{uuid.uuid4().hex[:24]}",
118
+ "type": "function",
119
+ "function": {
120
+ "name": call.get("name", ""),
121
+ "arguments": json.dumps(call.get("arguments", call.get("parameters", {}))),
122
+ },
123
+ })
124
+ continue
125
+ except json.JSONDecodeError:
126
+ pass
127
+
128
+ # ── Format B: XML <function=name><parameter=k>v</parameter> ──
129
+ fn_match = re.search(r'<function=([^>]+)>', stripped)
130
+ if fn_match:
131
+ fn_name = fn_match.group(1).strip()
132
+ args = {}
133
+ for param in re.finditer(r'<parameter=([^>]+)>(.*?)</parameter>', stripped, re.DOTALL):
134
+ key = param.group(1).strip()
135
+ val = param.group(2).strip()
136
+ # Try to coerce to int/float/bool, otherwise keep as string
137
+ try:
138
+ val = json.loads(val)
139
+ except (json.JSONDecodeError, ValueError):
140
+ pass
141
+ args[key] = val
142
+ tool_calls.append({
143
+ "id": f"call_{uuid.uuid4().hex[:24]}",
144
+ "type": "function",
145
+ "function": {
146
+ "name": fn_name,
147
+ "arguments": json.dumps(args),
148
+ },
149
+ })
150
+
151
+ if not tool_calls:
152
+ return None, text
153
+
154
+ remaining = re.sub(pattern, '', text, flags=re.DOTALL).strip()
155
+ return tool_calls, remaining or None
156
+
157
+
158
  def chat_completions(
159
  messages_json: str,
160
  max_tokens: int = 512,
161
  temperature: float = 0.7,
162
  top_p: float = 0.9,
163
+ tools_json: str = "",
164
  ) -> str:
165
  """
166
  Non-streaming chat completions. Returns an OpenAI-compatible JSON string.
167
 
168
+ messages_json: JSON array of {role, content} objects
169
+ tools_json: JSON array of OpenAI-format tool definitions (optional)
170
 
171
+ NOTE: Qwen3-Coder is non-thinking only; enable_thinking is not supported.
 
172
  """
173
  try:
174
  messages = json.loads(messages_json)
175
  except json.JSONDecodeError as e:
176
  return json.dumps({"error": f"Invalid messages_json: {e}"})
177
 
178
+ tools = None
179
+ if tools_json:
180
+ try:
181
+ tools = json.loads(tools_json)
182
+ except json.JSONDecodeError:
183
+ pass
184
+
185
  try:
186
+ hf_messages = []
187
+ for m in messages:
188
+ role = m["role"]
189
+ if role == "tool":
190
+ hf_messages.append({
191
+ "role": "tool",
192
+ "content": m.get("content", ""),
193
+ "tool_call_id": m.get("tool_call_id", ""),
194
+ })
195
+ elif role == "assistant" and m.get("tool_calls"):
196
+ hf_messages.append({
197
+ "role": "assistant",
198
+ "content": m.get("content") or "",
199
+ "tool_calls": m["tool_calls"],
200
+ })
201
+ else:
202
+ hf_messages.append({"role": role, "content": m.get("content", "")})
203
+
204
+ template_kwargs = dict(tokenize=False, add_generation_prompt=True)
205
+ if tools:
206
+ template_kwargs["tools"] = tools
207
+
208
+ prompt = tokenizer.apply_chat_template(hf_messages, **template_kwargs)
209
  except Exception as e:
210
  return json.dumps({"error": f"Prompt build failed: {e}"})
211
 
212
  gen_kwargs = dict(
213
  max_new_tokens=max_tokens,
214
+ temperature=max(temperature, 0.01),
215
  top_p=top_p,
216
  do_sample=True,
217
  pad_token_id=tokenizer.eos_token_id,
218
  )
219
 
220
  try:
221
+ raw = _generate_response(prompt, gen_kwargs)
222
  except Exception as e:
223
  return json.dumps({"error": f"Generation failed: {e}"})
224
 
225
  cid = f"chatcmpl-{uuid.uuid4().hex}"
226
+ tool_calls, content = _parse_tool_calls(raw)
227
+
228
+ if tool_calls:
229
+ message = {"role": "assistant", "content": content, "tool_calls": tool_calls}
230
+ finish_reason = "tool_calls"
231
+ else:
232
+ message = {"role": "assistant", "content": raw}
233
+ finish_reason = "stop"
234
+
235
  result = {
236
  "id": cid,
237
  "object": "chat.completion",
238
  "created": int(time.time()),
239
  "model": MODEL_ALIAS,
240
+ "choices": [{"index": 0, "message": message, "finish_reason": finish_reason}],
241
  "usage": {"prompt_tokens": -1, "completion_tokens": -1, "total_tokens": -1},
242
  }
243
  return json.dumps(result)
 
289
  _cc_max_tokens = gr.Number(label="max_tokens", value=512)
290
  _cc_temp = gr.Number(label="temperature", value=0.7)
291
  _cc_top_p = gr.Number(label="top_p", value=0.9)
292
+ _cc_tools = gr.Textbox(label="tools_json", value="")
293
  _cc_out = gr.Textbox(label="result")
294
  _cc_btn = gr.Button("chat_completions")
295
  _cc_btn.click(
296
  fn=chat_completions,
297
+ inputs=[_cc_messages, _cc_max_tokens, _cc_temp, _cc_top_p, _cc_tools],
298
  outputs=[_cc_out],
299
  api_name="chat_completions",
300
  )