(self, body: bytes)
| 134 | # ─── /v1/chat/completions ───────────────────────────────────────────────── |
| 135 | |
| 136 | def _handle_chat(self, body: bytes): |
| 137 | req = self._parse_body(body) |
| 138 | if req is None: |
| 139 | self.send_json({"error": {"message": "invalid JSON"}}, 400) |
| 140 | return |
| 141 | model_name, model_id, think_mode, err, extra_fields = resolve_model( |
| 142 | req.get("model", CONFIG["default_model"])) |
| 143 | if err: |
| 144 | self.send_json({"error": {"message": err}}, 400) |
| 145 | return |
| 146 | |
| 147 | tools = req.get("tools") |
| 148 | tool_choice = req.get("tool_choice", "auto") |
| 149 | prompt, images = messages_to_prompt(req.get("messages", []), tools, tool_choice) |
| 150 | if not prompt.strip(): |
| 151 | self.send_json({"error": {"message": "empty prompt"}}, 400) |
| 152 | return |
| 153 | |
| 154 | stream = req.get("stream", False) |
| 155 | cid = f"chatcmpl-{uuid.uuid4().hex[:12]}" |
| 156 | |
| 157 | if stream and (not tools or tool_choice == "none"): |
| 158 | try: |
| 159 | self._start_sse() |
| 160 | for delta in generate_stream(prompt, model_id, think_mode, _upload_images(images), extra_fields): |
| 161 | chunk = {"id": cid, "object": "chat.completion.chunk", "created": int(time.time()), |
| 162 | "model": model_name, "choices": [{"index": 0, "delta": {"content": delta}, "finish_reason": None}]} |
| 163 | self.wfile.write(f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n".encode()) |
| 164 | self.wfile.flush() |
| 165 | end = {"id": cid, "object": "chat.completion.chunk", "created": int(time.time()), |
| 166 | "model": model_name, "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}]} |
| 167 | self.wfile.write(f"data: {json.dumps(end)}\n\n".encode()) |
| 168 | self.wfile.write(b"data: [DONE]\n\n") |
| 169 | self.wfile.flush() |
| 170 | except (BrokenPipeError, ConnectionResetError): |
| 171 | pass |
| 172 | return |
| 173 | |
| 174 | try: |
| 175 | text = generate(prompt, model_id, think_mode, _upload_images(images), extra_fields) |
| 176 | except Exception as e: |
| 177 | self.send_json({"error": {"message": f"upstream error: {e}"}}, 502) |
| 178 | return |
| 179 | |
| 180 | tool_calls = None |
| 181 | if tools and text and tool_choice != "none": |
| 182 | text, tool_calls = parse_tool_calls(text) |
| 183 | msg = {"role": "assistant", "content": text or None} |
| 184 | if tool_calls: |
| 185 | msg["tool_calls"] = tool_calls |
| 186 | finish = "tool_calls" if tool_calls else "stop" |
| 187 | |
| 188 | if stream: |
| 189 | self._start_sse() |
| 190 | chunk = {"id": cid, "object": "chat.completion.chunk", "created": int(time.time()), |
| 191 | "model": model_name, "choices": [{"index": 0, "delta": msg, "finish_reason": finish}]} |
| 192 | self.wfile.write(f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n".encode()) |
| 193 | self.wfile.write(b"data: [DONE]\n\n") |
no test coverage detected