fomext commited on
Commit
62d5c00
·
verified ·
1 Parent(s): c4a519f

Upload app.py

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