feat: add /v1/fim/completions route with Qwen FIM token formatting for Continue
This commit is contained in:
@@ -248,6 +248,78 @@ async def _blocking_chat(payload: dict, model: str, conv_id: str, request: Reque
|
|||||||
return JSONResponse(content=_build_openai_response(assistant_msg, model, conv_id))
|
return JSONResponse(content=_build_openai_response(assistant_msg, model, conv_id))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/v1/fim/completions")
|
||||||
|
async def fim_completions(request: Request):
|
||||||
|
_check_api_key(request)
|
||||||
|
try:
|
||||||
|
body = await request.json()
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid JSON body")
|
||||||
|
|
||||||
|
prefix = body.get("prompt", "")
|
||||||
|
suffix = body.get("suffix", "")
|
||||||
|
stream = body.get("stream", True)
|
||||||
|
max_tokens = body.get("max_tokens", 128)
|
||||||
|
|
||||||
|
fim_prompt = f"<|fim_prefix|>{prefix}<|fim_suffix|>{suffix}<|fim_middle|>"
|
||||||
|
|
||||||
|
upstream = {
|
||||||
|
"prompt": fim_prompt,
|
||||||
|
"n_predict": max_tokens,
|
||||||
|
"temperature": body.get("temperature", 0),
|
||||||
|
"stop": body.get("stop", []),
|
||||||
|
"stream": True,
|
||||||
|
"cache_prompt": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _stream_fim():
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
try:
|
||||||
|
async with client.stream(
|
||||||
|
"POST", f"{LLAMA_SERVER_BASE}/completion",
|
||||||
|
json=upstream,
|
||||||
|
timeout=httpx.Timeout(30.0, connect=5.0),
|
||||||
|
) as resp:
|
||||||
|
async for line in resp.aiter_lines():
|
||||||
|
if not line or not line.startswith("data: "):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
d = json.loads(line[6:])
|
||||||
|
content = d.get("content", "")
|
||||||
|
if content:
|
||||||
|
chunk = {
|
||||||
|
"choices": [{"delta": {"content": content}, "index": 0}]
|
||||||
|
}
|
||||||
|
yield f"data: {json.dumps(chunk)}\n\n"
|
||||||
|
if d.get("stop"):
|
||||||
|
break
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
yield "data: [DONE]\n\n"
|
||||||
|
except httpx.ConnectError:
|
||||||
|
yield f"data: {json.dumps({'error': 'Cannot connect to inference server'})}\n\n"
|
||||||
|
|
||||||
|
if stream:
|
||||||
|
return StreamingResponse(_stream_fim(), media_type="text/event-stream")
|
||||||
|
|
||||||
|
# Non-streaming: accumulate and return
|
||||||
|
full = []
|
||||||
|
async for chunk in _stream_fim():
|
||||||
|
if chunk.startswith("data: [DONE]"):
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
d = json.loads(chunk[6:])
|
||||||
|
content = d.get("choices", [{}])[0].get("delta", {}).get("content", "")
|
||||||
|
if content:
|
||||||
|
full.append(content)
|
||||||
|
except (json.JSONDecodeError, IndexError):
|
||||||
|
pass
|
||||||
|
return JSONResponse(content={
|
||||||
|
"choices": [{"text": "".join(full), "index": 0, "finish_reason": "stop"}],
|
||||||
|
"usage": {},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
async def _fim_passthrough(body: dict) -> JSONResponse:
|
async def _fim_passthrough(body: dict) -> JSONResponse:
|
||||||
"""
|
"""
|
||||||
Proxy FIM requests directly to llama-server without pipeline injection.
|
Proxy FIM requests directly to llama-server without pipeline injection.
|
||||||
|
|||||||
Reference in New Issue
Block a user