fomext commited on
Commit
dd1b9ab
Β·
verified Β·
1 Parent(s): a8d2779

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -6
app.py CHANGED
@@ -85,9 +85,21 @@ def list_models() -> str:
85
 
86
  def _parse_tool_calls(text: str):
87
  """
88
- Detect and extract Hermes-style tool calls from model output.
89
- Returns (tool_calls, remaining_content) where tool_calls is a list in
90
- OpenAI format, or (None, text) if no tool calls are found.
 
 
 
 
 
 
 
 
 
 
 
 
91
  """
92
  pattern = r'<tool_call>(.*?)</tool_call>'
93
  matches = re.findall(pattern, text, re.DOTALL)
@@ -96,8 +108,11 @@ def _parse_tool_calls(text: str):
96
 
97
  tool_calls = []
98
  for match in matches:
 
 
 
99
  try:
100
- call = json.loads(match.strip())
101
  tool_calls.append({
102
  "id": f"call_{uuid.uuid4().hex[:24]}",
103
  "type": "function",
@@ -106,8 +121,32 @@ def _parse_tool_calls(text: str):
106
  "arguments": json.dumps(call.get("arguments", call.get("parameters", {}))),
107
  },
108
  })
109
- except json.JSONDecodeError:
110
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  if not tool_calls:
113
  return None, text
@@ -154,10 +193,31 @@ def chat_completions(
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", "")})
 
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)
 
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",
 
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
 
193
  "tool_call_id": m.get("tool_call_id", ""),
194
  })
195
  elif role == "assistant" and m.get("tool_calls"):
196
+ # Normalise tool_calls: apply_chat_template needs arguments as a dict,
197
+ # but OpenAI format (and our own output) stores them as a JSON string.
198
+ normalised_tool_calls = []
199
+ for tc in m["tool_calls"]:
200
+ fn = tc.get("function", {})
201
+ raw_args = fn.get("arguments", "{}")
202
+ if isinstance(raw_args, str):
203
+ try:
204
+ parsed_args = json.loads(raw_args)
205
+ except json.JSONDecodeError:
206
+ parsed_args = {}
207
+ else:
208
+ parsed_args = raw_args # already a dict
209
+ normalised_tool_calls.append({
210
+ "id": tc.get("id", ""),
211
+ "type": "function",
212
+ "function": {
213
+ "name": fn.get("name", ""),
214
+ "arguments": parsed_args, # dict, not string
215
+ },
216
+ })
217
  hf_messages.append({
218
  "role": "assistant",
219
  "content": m.get("content") or "",
220
+ "tool_calls": normalised_tool_calls,
221
  })
222
  else:
223
  hf_messages.append({"role": role, "content": m.get("content", "")})