Files
ragflow/internal/handler/openai_chat.go
qinling0210 563d855780 Implement OpenAI chat completions in GO (#16177)
### What problem does this PR solve?

Implement OpenAI chat completions in GO

POST /api/v1/openai/<chat_id>/chat/completions

OpenAI chat cli: internal/development.md

### Type of change

- [x] Refactoring
2026-06-18 18:07:27 +08:00

126 lines
3.6 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package handler
import (
"encoding/json"
"io"
"ragflow/internal/common"
"ragflow/internal/service"
"github.com/gin-gonic/gin"
)
type OpenAIChatHandler struct {
svc *service.OpenAIChatService
}
func NewOpenAIChatHandler(svc *service.OpenAIChatService) *OpenAIChatHandler {
return &OpenAIChatHandler{svc: svc}
}
// OpenAIChatCompletions handles the OpenAI-compatible chat completions route.
// @Summary OpenAI Chat Completions
// @Description OpenAI-compatible chat completions endpoint
// @Tags openai
// @Accept json
// @Produce json
// @Param chat_id path string true "dialog id"
// @Param request body service.OpenAIChatRequest true "chat completion request"
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/openai/{chat_id}/chat/completions [post]
func (h *OpenAIChatHandler) OpenAIChatCompletions(c *gin.Context) {
chatID := c.Param("chat_id")
if chatID == "" {
jsonError(c, common.CodeDataError, "You don't own the chat "+chatID)
return
}
user, code, msg := GetUser(c)
if code != common.CodeSuccess {
jsonError(c, code, msg)
return
}
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
jsonError(c, common.CodeArgumentError, err.Error())
return
}
// Parse body into the typed request
var req service.OpenAIChatRequest
if err := json.Unmarshal(bodyBytes, &req); err != nil {
jsonError(c, common.CodeArgumentError, err.Error())
return
}
// Messages presence
if len(req.Messages) == 0 {
jsonError(c, common.CodeDataError, "You have to provide messages.")
return
}
// extra_body shape validation
extraBody, extraBodyOK := req.ExtraBody.(map[string]interface{})
if req.ExtraBody != nil && !extraBodyOK {
jsonError(c, common.CodeArgumentError, "extra_body must be an object.")
return
}
// reference_metadata shape validation
if extraBody != nil {
if rm, ok := extraBody["reference_metadata"].(map[string]interface{}); ok {
if rawFields, has := rm["fields"]; has {
if rawArr, ok := rawFields.([]interface{}); !ok {
jsonError(c, common.CodeArgumentError, "reference_metadata.fields must be an array.")
return
} else {
for _, item := range rawArr {
if _, ok := item.(string); !ok {
jsonError(c, common.CodeArgumentError, "reference_metadata.fields must be an array.")
return
}
}
}
}
}
}
// metadata_condition shape validation
if extraBody != nil {
if mc, ok := extraBody["metadata_condition"]; ok && mc != nil {
if _, ok := mc.(map[string]interface{}); !ok {
jsonError(c, common.CodeArgumentError, "metadata_condition must be an object.")
return
}
}
}
// Last message must be from the user
if last := req.Messages[len(req.Messages)-1]; last != nil {
if role, _ := last["role"].(string); role != "user" {
jsonError(c, common.CodeDataError, "The last content of this conversation is not from user.")
return
}
}
// All early-rejection checks passed. Delegate to the service for the
// actual LLM call.
h.svc.OpenAIChatCompletions(c, user.ID, chatID, bodyBytes)
}