fomext commited on
Commit
0bb09b3
·
verified ·
1 Parent(s): 97e410d

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -12
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import json
 
2
  import time
3
  import uuid
4
  from typing import Optional
@@ -77,55 +78,133 @@ def list_models() -> str:
77
  return json.dumps(result)
78
 
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  def chat_completions(
81
  messages_json: str,
82
  max_tokens: int = 512,
83
  temperature: float = 0.7,
84
  top_p: float = 0.9,
85
  enable_thinking: bool = False,
 
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
  try:
94
  messages = json.loads(messages_json)
95
  except json.JSONDecodeError as e:
96
  return json.dumps({"error": f"Invalid messages_json: {e}"})
97
 
 
 
 
 
 
 
 
98
  try:
99
- hf_messages = [{"role": m["role"], "content": m["content"]} for m in messages]
100
- prompt = tokenizer.apply_chat_template(
101
- hf_messages,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  tokenize=False,
103
  add_generation_prompt=True,
104
  enable_thinking=enable_thinking,
105
  )
 
 
 
 
106
  except Exception as e:
107
  return json.dumps({"error": f"Prompt build failed: {e}"})
108
 
109
  gen_kwargs = dict(
110
  max_new_tokens=max_tokens,
111
- temperature=temperature,
112
  top_p=top_p,
113
  do_sample=True,
114
  pad_token_id=tokenizer.eos_token_id,
115
  )
116
 
117
  try:
118
- content = _generate_response(prompt, gen_kwargs)
119
  except Exception as e:
120
  return json.dumps({"error": f"Generation failed: {e}"})
121
 
122
  cid = f"chatcmpl-{uuid.uuid4().hex}"
 
 
 
 
 
 
 
 
 
 
 
123
  result = {
124
  "id": cid,
125
  "object": "chat.completion",
126
  "created": int(time.time()),
127
  "model": MODEL_ALIAS,
128
- "choices": [{"index": 0, "message": {"role": "assistant", "content": content}, "finish_reason": "stop"}],
129
  "usage": {"prompt_tokens": -1, "completion_tokens": -1, "total_tokens": -1},
130
  }
131
  return json.dumps(result)
@@ -184,15 +263,16 @@ You can also chat directly below.
184
  with gr.Row(visible=False):
185
  # -- chat_completions --------------------------------------------
186
  _cc_messages = gr.Textbox(label="messages_json")
187
- _cc_max_tokens = gr.Number(label="max_tokens", value=512)
188
- _cc_temp = gr.Number(label="temperature", value=0.7)
189
- _cc_top_p = gr.Number(label="top_p", value=0.9)
190
  _cc_thinking = gr.Checkbox(label="enable_thinking", value=False)
 
191
  _cc_out = gr.Textbox(label="result")
192
  _cc_btn = gr.Button("chat_completions")
193
  _cc_btn.click(
194
  fn=chat_completions,
195
- inputs=[_cc_messages, _cc_max_tokens, _cc_temp, _cc_top_p, _cc_thinking],
196
  outputs=[_cc_out],
197
  api_name="chat_completions",
198
  )
 
1
  import json
2
+ import re
3
  import time
4
  import uuid
5
  from typing import Optional
 
78
  return json.dumps(result)
79
 
80
 
81
+ # ---------------------------------------------------------------------------
82
+ # Tool call parsing (Hermes-style: <tool_call>{...}</tool_call>)
83
+ # ---------------------------------------------------------------------------
84
+
85
+ def _parse_tool_calls(text: str):
86
+ """
87
+ Detect and extract Hermes-style tool calls from model output.
88
+ Returns (tool_calls, remaining_content) where tool_calls is a list in
89
+ OpenAI format, or (None, text) if no tool calls are found.
90
+ """
91
+ pattern = r'<tool_call>(.*?)</tool_call>'
92
+ matches = re.findall(pattern, text, re.DOTALL)
93
+ if not matches:
94
+ return None, text
95
+
96
+ tool_calls = []
97
+ for match in matches:
98
+ try:
99
+ call = json.loads(match.strip())
100
+ tool_calls.append({
101
+ "id": f"call_{uuid.uuid4().hex[:24]}",
102
+ "type": "function",
103
+ "function": {
104
+ "name": call.get("name", ""),
105
+ "arguments": json.dumps(call.get("arguments", call.get("parameters", {}))),
106
+ },
107
+ })
108
+ except json.JSONDecodeError:
109
+ continue
110
+
111
+ if not tool_calls:
112
+ return None, text
113
+
114
+ remaining = re.sub(pattern, '', text, flags=re.DOTALL)
115
+ remaining = re.sub(r'<think>.*?</think>', '', remaining, flags=re.DOTALL).strip()
116
+ return tool_calls, remaining or None
117
+
118
+
119
  def chat_completions(
120
  messages_json: str,
121
  max_tokens: int = 512,
122
  temperature: float = 0.7,
123
  top_p: float = 0.9,
124
  enable_thinking: bool = False,
125
+ tools_json: str = "",
126
  ) -> str:
127
  """
128
  Non-streaming chat completions. Returns an OpenAI-compatible JSON string.
129
 
130
+ messages_json: JSON array of {role, content} objects
131
+ enable_thinking: enable Qwen3 chain-of-thought reasoning (default False for tool use)
132
+ tools_json: JSON array of OpenAI-format tool definitions (optional)
133
  """
134
  try:
135
  messages = json.loads(messages_json)
136
  except json.JSONDecodeError as e:
137
  return json.dumps({"error": f"Invalid messages_json: {e}"})
138
 
139
+ tools = None
140
+ if tools_json:
141
+ try:
142
+ tools = json.loads(tools_json)
143
+ except json.JSONDecodeError:
144
+ pass
145
+
146
  try:
147
+ hf_messages = []
148
+ for m in messages:
149
+ role = m["role"]
150
+ if role == "tool":
151
+ hf_messages.append({
152
+ "role": "tool",
153
+ "content": m.get("content", ""),
154
+ "tool_call_id": m.get("tool_call_id", ""),
155
+ })
156
+ elif role == "assistant" and m.get("tool_calls"):
157
+ hf_messages.append({
158
+ "role": "assistant",
159
+ "content": m.get("content") or "",
160
+ "tool_calls": m["tool_calls"],
161
+ })
162
+ else:
163
+ hf_messages.append({"role": role, "content": m.get("content", "")})
164
+
165
+ template_kwargs = dict(
166
  tokenize=False,
167
  add_generation_prompt=True,
168
  enable_thinking=enable_thinking,
169
  )
170
+ if tools:
171
+ template_kwargs["tools"] = tools
172
+
173
+ prompt = tokenizer.apply_chat_template(hf_messages, **template_kwargs)
174
  except Exception as e:
175
  return json.dumps({"error": f"Prompt build failed: {e}"})
176
 
177
  gen_kwargs = dict(
178
  max_new_tokens=max_tokens,
179
+ temperature=max(temperature, 0.01),
180
  top_p=top_p,
181
  do_sample=True,
182
  pad_token_id=tokenizer.eos_token_id,
183
  )
184
 
185
  try:
186
+ raw = _generate_response(prompt, gen_kwargs)
187
  except Exception as e:
188
  return json.dumps({"error": f"Generation failed: {e}"})
189
 
190
  cid = f"chatcmpl-{uuid.uuid4().hex}"
191
+ tool_calls, content = _parse_tool_calls(raw)
192
+
193
+ if tool_calls:
194
+ message = {"role": "assistant", "content": content, "tool_calls": tool_calls}
195
+ finish_reason = "tool_calls"
196
+ else:
197
+ # Strip think tags from plain responses when thinking was enabled
198
+ content = re.sub(r'<think>.*?</think>', '', raw, flags=re.DOTALL).strip()
199
+ message = {"role": "assistant", "content": content}
200
+ finish_reason = "stop"
201
+
202
  result = {
203
  "id": cid,
204
  "object": "chat.completion",
205
  "created": int(time.time()),
206
  "model": MODEL_ALIAS,
207
+ "choices": [{"index": 0, "message": message, "finish_reason": finish_reason}],
208
  "usage": {"prompt_tokens": -1, "completion_tokens": -1, "total_tokens": -1},
209
  }
210
  return json.dumps(result)
 
263
  with gr.Row(visible=False):
264
  # -- chat_completions --------------------------------------------
265
  _cc_messages = gr.Textbox(label="messages_json")
266
+ _cc_max_tokens = gr.Number(label="max_tokens", value=512)
267
+ _cc_temp = gr.Number(label="temperature", value=0.7)
268
+ _cc_top_p = gr.Number(label="top_p", value=0.9)
269
  _cc_thinking = gr.Checkbox(label="enable_thinking", value=False)
270
+ _cc_tools = gr.Textbox(label="tools_json", value="")
271
  _cc_out = gr.Textbox(label="result")
272
  _cc_btn = gr.Button("chat_completions")
273
  _cc_btn.click(
274
  fn=chat_completions,
275
+ inputs=[_cc_messages, _cc_max_tokens, _cc_temp, _cc_top_p, _cc_thinking, _cc_tools],
276
  outputs=[_cc_out],
277
  api_name="chat_completions",
278
  )