fix(openai): store 요청 필드를 호환 처리한다
This commit is contained in:
parent
a342d584c5
commit
9529af8dfc
4 changed files with 27 additions and 2 deletions
|
|
@ -164,6 +164,7 @@ Workspace-bound route는 workspace가 없거나 상대 경로이면 OpenAI-compa
|
|||
- `tool_choice`
|
||||
- `parallel_tool_calls`
|
||||
- `stream_options`
|
||||
- `store`
|
||||
|
||||
`tools`가 있는 Chat Completions 요청에서 provider route(`openai_compat`, `vllm`, `ollama`, provider pool)는 forced tool 선택 객체와 `"none"` 같은 명시적 `tool_choice`를 backend에 전달한다. 단, `"auto"`는 OpenAI-compatible 기본값과 같으므로 provider request에서는 생략한다. 일부 vLLM 계열 backend는 explicit/default `"auto"`를 `--enable-auto-tool-choice`/`--tool-call-parser` 없이 400으로 거부한다. 이 400이 발생하고 요청 tool이 정확히 1개이면 Node adapter는 해당 tool에 대한 forced `tool_choice`로 1회 재시도한다. forced tool도 `--tool-call-parser` 요구로 거부되거나 여러 tool이라 forced를 고를 수 없으면, Node adapter는 `tools`/`tool_choice`를 제거하고 text tool-call system instruction을 leading system message에 병합해 1회 재시도하며 완료 metadata에 `openai_text_tool_fallback: "true"`를 싣는다.
|
||||
provider가 native OpenAI-compatible `tool_calls`를 반환하면 Node는 내부 `RunEvent.metadata["openai_tool_calls"]` JSON으로 보존하고, Edge는 이를 OpenAI-compatible `message.tool_calls` 또는 stream `delta.tool_calls`로 반환하며 `finish_reason: "tool_calls"`를 사용한다.
|
||||
|
|
@ -173,7 +174,7 @@ CLI route(`adapter: "cli"`)는 native backend tool calling이 없으므로, 호
|
|||
CLI fallback에서 텍스트 tool call을 구조화할 때만 Edge는 요청의 `tools[].function.parameters` schema를 기준으로 arguments를 정규화한다. 예를 들어 tool schema가 `commands: string[]`만 허용하면 command 객체 입력도 shell string 배열로 접고, `commands: {command,args}[]`를 허용하면 shell 문법이 없는 명령을 structured argv로 만든다.
|
||||
CLI fallback에서 schema에 없는 UI 설명용 `description`이나 실행 위치 힌트용 `runInTerminal`은 command 객체와 최상위 args에서 제거한다. 단, `cd`, `command -v`, `&&`, pipe, redirect, quote 등 shell 해석이 필요한 명령은 schema가 허용할 때 `commands: ["cd /work && git status"]` 같은 shell string으로 유지한다.
|
||||
CLI route에서는 backend auto tool-calling 요구 조건으로 요청이 실패하지 않도록 내부 실행 입력의 `tool_choice`를 `"none"`으로 낮춘다.
|
||||
`parallel_tool_calls`와 `stream_options`는 클라이언트 호환성을 위해 수신하지만 현재 Edge 실행 의미에는 반영하지 않는다.
|
||||
`parallel_tool_calls`, `stream_options`, `store`는 클라이언트 호환성을 위해 수신하지만 현재 Edge 실행 의미에는 반영하지 않는다.
|
||||
|
||||
금지:
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ func decodeChatCompletionRequest(dec *json.Decoder, req *chatCompletionRequest)
|
|||
}
|
||||
for key := range raw {
|
||||
switch key {
|
||||
case "model", "messages", "stream", "metadata", "max_tokens", "max_completion_tokens", "temperature", "top_p", "presence_penalty", "frequency_penalty", "seed", "stop", "response_format", "tools", "tool_choice", "parallel_tool_calls", "stream_options":
|
||||
case "model", "messages", "stream", "metadata", "max_tokens", "max_completion_tokens", "temperature", "top_p", "presence_penalty", "frequency_penalty", "seed", "stop", "response_format", "tools", "tool_choice", "parallel_tool_calls", "stream_options", "store":
|
||||
default:
|
||||
return fmt.Errorf("%s is not supported for /v1/chat/completions", key)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,6 +232,29 @@ func TestChatCompletionsPassesStandardOptions(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestChatCompletionsAcceptsStoreNoOp(t *testing.T) {
|
||||
fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)}
|
||||
fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"}
|
||||
fake.events <- &iop.RunEvent{Type: "complete"}
|
||||
|
||||
srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
|
||||
"model":"from-request",
|
||||
"messages":[{"role":"user","content":"hi"}],
|
||||
"store":false
|
||||
}`))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
srv.handleChatCompletions(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
if _, ok := fake.req.Input["store"]; ok {
|
||||
t.Fatalf("store must be accepted as a client compatibility no-op: %+v", fake.req.Input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatCompletionsOmitsProviderToolChoiceAuto(t *testing.T) {
|
||||
fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)}
|
||||
fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ type chatCompletionRequest struct {
|
|||
ToolChoice any `json:"tool_choice,omitempty"`
|
||||
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
|
||||
StreamOptions any `json:"stream_options,omitempty"`
|
||||
Store any `json:"store,omitempty"`
|
||||
}
|
||||
|
||||
type chatMessage struct {
|
||||
|
|
|
|||
Loading…
Reference in a new issue