Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

/v1/completions: `prompt: []` returns 200 with no choices, and both completions paths drop the error code (context_length_exceeded)

オープン 初心者向け
#767 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
84/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
fastapi, python
領域
api, backend

調査の方向性

python/freetoken/server/openai_api.pyから始め、特に_completion_unsupported_reasonと、issueで挙げられているストリーミングおよび非ストリーミングのエラーパスを確認してください。チャットcompletionのルートがエラーコードをどのように渡すかを調べ、api_models.pyのcompletionリクエストの検証と比較してください。空のプロンプトリストが400を返し、両方のcompletionエラーパスでcontext_length_exceededが保持されることを完了条件とします。issueにあるCPUのみの再現を実行してください。

索引モデルが issue の本文から書いたものです。

説明

What happened
  1. /v1/completions with "prompt": [] returns HTTP 200 with "choices": [] and zero usage. Nothing is sent to the engine. The client asked for completions of zero prompts, and the request should get a 400.
  2. If the scheduler rejects a completions request with a classified error (context_length_exceeded for a prompt that is too long), the client gets "code": null. This happens in the non-streaming 400 and in the streamed error event. /v1/chat/completions returns the same failure with "code": "context_length_exceeded", so a client that checks the code (to trim and retry, for example) works on chat but not on completions.

Minimal reproduction (CPU only, PYTHONPATH=python):

import asyncio
from types import SimpleNamespace
from freetoken.message import UserReply
from freetoken.server.openai_api import CompletionRequest, handle_completion

class State:
    def __init__(self, replies):
        self.config = SimpleNamespace(served_model_name="m", max_output_tokens=None)
        self.replies, self.sent = replies, []
    def new_user(self): return 1
    async def send_one(self, msg): self.sent.append(msg)
    async def wait_for_ack(self, uid):
        for r in self.replies: yield r

async def body(r):
    if hasattr(r, "body_iterator"):
        return b"".join([c async for c in r.body_iterator]).decode().splitlines()[0]
    return f"HTTP {r.status_code} {r.body.decode()}" if hasattr(r, "body") else f"HTTP 200 {r['choices']} {r['usage']}"

err = UserReply(uid=1, incremental_output="", finished=True,
                error="prompt is too long: 9000 tokens > 8192 maximum", error_code="context_length_exceeded")
async def main():
    s = State([])
    print("prompt=[]    ->", await body(await handle_completion(CompletionRequest(model="m", prompt=[]), None, s, {})), "| sent:", len(s.sent))
    for stream in (False, True):
        r = await handle_completion(CompletionRequest(model="m", prompt="x", stream=stream), None, State([err]), {})
        print(f"stream={stream} ->", await body(r))
asyncio.run(main())
prompt=[]    -> HTTP 200 [] {'prompt_tokens': 0, 'completion_tokens': 0, 'total_tokens': 0} | sent: 0
stream=False -> HTTP 400 {"error":{"message":"prompt is too long: 9000 tokens > 8192 maximum","type":"invalid_request_error","param":null,"code":null}}
stream=True -> data: {"error": {"message": "prompt is too long: 9000 tokens > 8192 maximum", "type": "invalid_request_error", "code": null}}
Cause (current main, cc56b6bd)
  • python/freetoken/server/openai_api.py:407-427: prompts = req.prompt for a list, and the loop over an empty list runs zero times. _is_token_prompt (:671-679) returns False for [] (bool(prompt)), so _completion_unsupported_reason lets it through. CompletionRequest.prompt (api_models.py:105) has no minimum length. ([""] does reach the tokenizer, which rejects it with "prompt must contain at least one token", tokenizer/server.py:110-121.)
  • openai_api.py:442: the non-streaming loop returns create_error_response(ack.error) without code=, although UserReply.error_code (message/frontend.py:55) carries it. :469 (stream) hardcodes "code": None. The chat route passes it on (GenerationError.code, openai_api.py:204, :215, and the stream error chunk at :274).
Proposed fix
  • Return 400 for an empty prompt list ("prompt must not be empty", param="prompt") in _completion_unsupported_reason, or with min_length=1 on the field.
  • Use create_error_response(ack.error, code=getattr(ack, "error_code", None)) at :442, and "code": getattr(ack, "error_code", None) at :469.

(#749 covers a related problem: a streamed request that is too long gets 200 and then an in-stream error. It does not cover the dropped code.)

Environment

Seen serving nvidia/Qwen3.8-Flash-Next-NVFP4 on an RTX 5080 16 GB (sm_120, driver 615.78.08), torch 2.14.1+cu130; reproduction CPU-only on main cc56b6bd.

主要言語
Python
スター
14.2k
フォーク
1.4k
平均マージ
21時間 39分
マージ済み PR(30日)
100

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

FlashML-org/FreeToken のほかの issue

FlashML-org/FreeToken の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。