From 326542f480f0c539c86980d80a2ec57142819531 Mon Sep 17 00:00:00 2001 From: Concedo <39025047+LostRuins@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:38:08 +0800 Subject: [PATCH] rudimentary responses api, not usable yet --- embd_res/kcpp_docs.embd | 22 ++++++++++++++++++++++ koboldcpp.py | 23 +++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/embd_res/kcpp_docs.embd b/embd_res/kcpp_docs.embd index a506d0f0c..17fde2a81 100644 --- a/embd_res/kcpp_docs.embd +++ b/embd_res/kcpp_docs.embd @@ -2798,6 +2798,28 @@ "responses": {"default": {"description": ""}} } }, + "/v1/responses": { + "post": { + "summary": "Emulates OpenAI Responses API. Very experimental, not recommended (use chat completions instead). Please refer to OpenAI documentation", + "description": "Emulates OpenAI Responses API.\n\n This is an OpenAI compatibility endpoint.\n\n Please refer to OpenAI documentation at [https://developers.openai.com/api/reference/resources/responses](https://developers.openai.com/api/reference/resources/responses). All KoboldCpp samplers are supported, please refer to /api/v1/generate for more details.", + "requestBody": { + "content": { + "application/json": { + "example": {"model":"kcpp","input": "Tell me a joke about Kobolds."}, + "schema": { + "properties": {}, + "type": "object" + } + } + }, + "required": true + }, + "tags": [ + "v1" + ], + "responses": {"default": {"description": ""}} + } + }, "/v1/models": { "get": { "summary": "List and describe the various models available in the API. Please refer to OpenAI documentation", diff --git a/koboldcpp.py b/koboldcpp.py index 96fe20ea3..e819d5e02 100755 --- a/koboldcpp.py +++ b/koboldcpp.py @@ -3636,6 +3636,20 @@ ws ::= | " " | "\n" [ \t]{0,20} genparams["ollamasysprompt"] = ollamasysprompt genparams["ollamabodyprompt"] = ollamabodyprompt genparams["prompt"] = ollamasysprompt + ollamabodyprompt + elif api_format==8: # OpenAI Responses API, oai-responses + raw_input = genparams.get('input', '') + raw_instructions = genparams.get('instructions', '') + if isinstance(raw_input, str): + genparams['messages'] = [{"role": "user", "content": raw_input}] + elif isinstance(raw_input, list): + genparams['messages'] = raw_input + else: + genparams['messages'] = [] + if raw_instructions and isinstance(raw_instructions, str) and raw_instructions!="": + genparams['messages'].insert(0, {"role": "system", "content": raw_instructions}) + genparams['stream'] = False + transform_genparams(genparams, 4, use_jinja) # Delegate to the chat-completions transform by re-running as format 4 + return genparams #final transformations (universal template replace) replace_instruct_placeholders = genparams.get('replace_instruct_placeholders', True) @@ -3788,7 +3802,7 @@ class KcppProxyHandler(http.server.BaseHTTPRequestHandler): is_chat_completions_path = (self.path.endswith('/v1/chat/completions') or self.path=='/chat/completions') #any requests to the following endpoints is capable of waking the server - wake_requests = ["/api/extra/generate/stream","/api/extra/tokencount","/api/v1/generate","/sdapi/v1/interrogate","/v1/completions","/v1/chat/completions","/api/extra/transcribe","/v1/audio/transcriptions","/api/extra/tts","/v1/audio/speech","/api/extra/embeddings","/v1/embeddings","/api/extra/music/prepare","/api/extra/music/generate","/sdapi/v1/txt2img","/sdapi/v1/img2img","/sdapi/v1/upscale"] + wake_requests = ["/api/extra/generate/stream","/api/extra/tokencount","/api/v1/generate","/sdapi/v1/interrogate","/v1/completions","/v1/chat/completions","/v1/responses","/api/extra/transcribe","/v1/audio/transcriptions","/api/extra/tts","/v1/audio/speech","/api/extra/embeddings","/v1/embeddings","/api/extra/music/prepare","/api/extra/music/generate","/sdapi/v1/txt2img","/sdapi/v1/img2img","/sdapi/v1/upscale"] is_wake_request = self.path in wake_requests if is_post and (is_completions_path or is_chat_completions_path or is_wake_request): @@ -4096,6 +4110,9 @@ class KcppServerRequestHandler(http.server.SimpleHTTPRequestHandler): res = {"model": friendlymodelname,"created_at": str(datetime.now(timezone.utc).isoformat()),"response":recvtxt,"done": True,"done_reason":currfinishreason,"context": tokarr,"total_duration": 1,"load_duration": 1,"prompt_eval_count": prompttokens,"prompt_eval_duration": 1,"eval_count": comptokens,"eval_duration": 1} elif api_format == 7: res = {"model": friendlymodelname,"created_at": str(datetime.now(timezone.utc).isoformat()),"message":{"role":"assistant","content":recvtxt},"done": True,"done_reason":currfinishreason,"total_duration": 1,"load_duration": 1,"prompt_eval_count": prompttokens,"prompt_eval_duration": 1,"eval_count": comptokens,"eval_duration": 1} + elif api_format == 8: + resp_id = f"resp-A{genparams.get('oai_uniqueid', 1)}" + res = {"id": resp_id, "object": "response", "created_at": int(time.time()), "model": friendlymodelname, "output": [ { "type": "message", "role": "assistant", "content": [ {"type": "output_text", "text": recvtxt} ] } ], "usage": { "input_tokens": prompttokens, "output_tokens": comptokens, "total_tokens": prompttokens + comptokens } } else: #kcpp format res = {"results": [{"text": recvtxt, "tool_calls": tool_calls, "finish_reason": currfinishreason, "logprobs":logprobsdict, "prompt_tokens": prompttokens, "completion_tokens": comptokens}]} @@ -5325,7 +5342,7 @@ Change Mode
# handle endpoints that require mutex locking and handle actual gens try: sse_stream_flag = False - api_format = 0 #1=basic,2=kai,3=oai,4=oai-chat,5=interrogate,6=ollama,7=ollamachat + api_format = 0 #1=basic,2=kai,3=oai,4=oai-chat,5=interrogate,6=ollama,7=ollamachat,8=oai-responses is_imggen = False is_comfyui_imggen = False is_oai_imggen = False @@ -5418,6 +5435,8 @@ Change Mode
api_format = 6 elif self.path.endswith('/api/chat'): #ollama api_format = 7 + elif self.path.endswith('/v1/responses') or self.path=='/responses': #oai-responses + api_format = 8 elif self.path.endswith('/sdapi/v1/extra-single-image') or self.path.endswith('/sdapi/v1/upscale'): is_img_upscale = True elif self.path=="/prompt" or self.path=="/images/generations" or self.path.endswith('/v1/images/generations') or self.path.endswith('/sdapi/v1/txt2img') or self.path.endswith('/sdapi/v1/img2img'):