feat: add support for custom OpenAI endpoint path

- Introduced a new endpoint option for OpenAI integrations, allowing users to specify a custom path.
- Updated relevant components and validation logic to accommodate the new endpoint.
- Enhanced documentation and error messages to reflect the addition of the custom endpoint option.
This commit is contained in:
上玄
2026-07-04 18:51:41 +08:00
parent 4ec7b4289f
commit c50e7d0606
8 changed files with 226 additions and 18 deletions
+38 -9
View File
@@ -304,15 +304,44 @@ func OpenAIEndpointURL(baseURL string, endpoint string) string {
if !strings.HasPrefix(normalizedEndpoint, "/") {
normalizedEndpoint = "/" + normalizedEndpoint
}
// 规则0:自定义路径模式 → 直接用 baseURL 作为完整请求地址
if normalizedEndpoint == modelchannel.OpenAIEndpointCustom {
return base
}
// 规则1baseURL 已含 endpoint 后缀 → 直接用 base
if OpenAIEndpointFromBaseURL(base) != "" {
return base
}
if strings.HasSuffix(base, "/v1") && strings.HasPrefix(normalizedEndpoint, "/v1/") {
return base + strings.TrimPrefix(normalizedEndpoint, "/v1")
// 规则2:通用版本段去重(/v1 /v2 /v3 /v4 ... 任意版本号)
if version, ok := trailingVersionSegment(base); ok {
prefix := "/" + version + "/"
if strings.HasPrefix(normalizedEndpoint, prefix) {
return base + strings.TrimPrefix(normalizedEndpoint, "/"+version)
}
}
// 规则3:兜底原样拼接
return base + normalizedEndpoint
}
// trailingVersionSegment 检测 URL 末尾是否以 /vN 形式结尾(N 为数字),
// 返回版本段(如 "v4")和是否匹配。用于通用版本段去重。
func trailingVersionSegment(base string) (string, bool) {
idx := strings.LastIndex(base, "/")
if idx < 0 {
return "", false
}
seg := base[idx+1:]
if len(seg) < 2 || seg[0] != 'v' {
return "", false
}
for i := 1; i < len(seg); i++ {
if seg[i] < '0' || seg[i] > '9' {
return "", false
}
}
return seg, true
}
func ResolveOpenAIEndpoint(baseURL string, endpoint string) string {
if endpointFromURL := OpenAIEndpointFromBaseURL(baseURL); endpointFromURL != "" {
return endpointFromURL
@@ -377,11 +406,11 @@ func (adapter *OpenAIAdapter) Stream(ctx context.Context, req StreamRequest, sin
req.OpenAIEndpoint = endpoint
if req.RequestKnobs != nil {
req.RequestKnobs["openai_endpoint"] = endpoint
if endpoint == modelchannel.OpenAIEndpointResponses {
if modelchannel.OpenAIEndpointShape(endpoint) == "responses" {
req.RequestKnobs["max_output_tokens"] = req.MaxTokens
}
}
if endpoint == modelchannel.OpenAIEndpointResponses {
if modelchannel.OpenAIEndpointShape(endpoint) == "responses" {
return adapter.streamResponses(ctx, req, baseURL, apiKey, modelID, sink)
}
return adapter.streamChatCompletions(ctx, req, baseURL, apiKey, modelID, sink)
@@ -425,14 +454,14 @@ func (adapter *OpenAIAdapter) streamChatCompletions(ctx context.Context, req Str
recordLLMSummaryArtifact(req, buildLLMSummaryPayload(req, "openai", modelID, startedAt, time.Time{}, finishedAt, "", 0, 0, 0, 0, err))
return err
}
applyOpenAIThinkingDisable(bodyMap, req, baseURL, modelID, modelchannel.OpenAIEndpointChatCompletions)
applyOpenAIThinkingDisable(bodyMap, req, baseURL, modelID, req.OpenAIEndpoint)
if err := ApplyOpenAIExtraParams(bodyMap, req.OpenAIExtraParamsEnabled, req.OpenAIExtraParamsJSON); err != nil {
finishedAt = time.Now().UTC()
recordLLMSummaryArtifact(req, buildLLMSummaryPayload(req, "openai", modelID, startedAt, time.Time{}, finishedAt, "", 0, 0, 0, 0, err))
return err
}
body = bodyMap
requestURL := OpenAIEndpointURL(baseURL, modelchannel.OpenAIEndpointChatCompletions)
requestURL := OpenAIEndpointURL(baseURL, req.OpenAIEndpoint)
recordLLMRequestArtifact(req, "openai", modelID, "POST", requestURL, body)
payload, err := json.Marshal(body)
@@ -901,7 +930,7 @@ func (adapter *OpenAIAdapter) streamResponses(ctx context.Context, req StreamReq
recordLLMSummaryArtifact(req, buildLLMSummaryPayload(req, "openai", modelID, startedAt, time.Time{}, finishedAt, "", 0, 0, 0, 0, err))
return err
}
applyOpenAIThinkingDisable(bodyMap, req, baseURL, modelID, modelchannel.OpenAIEndpointResponses)
applyOpenAIThinkingDisable(bodyMap, req, baseURL, modelID, req.OpenAIEndpoint)
if err := ApplyOpenAIExtraParams(bodyMap, req.OpenAIExtraParamsEnabled, req.OpenAIExtraParamsJSON); err != nil {
finishedAt = time.Now().UTC()
recordLLMSummaryArtifact(req, buildLLMSummaryPayload(req, "openai", modelID, startedAt, time.Time{}, finishedAt, "", 0, 0, 0, 0, err))
@@ -909,7 +938,7 @@ func (adapter *OpenAIAdapter) streamResponses(ctx context.Context, req StreamReq
}
body = bodyMap
requestURL := OpenAIEndpointURL(baseURL, modelchannel.OpenAIEndpointResponses)
requestURL := OpenAIEndpointURL(baseURL, req.OpenAIEndpoint)
recordLLMRequestArtifact(req, "openai", modelID, "POST", requestURL, body)
payload, err := json.Marshal(body)
@@ -1819,7 +1848,7 @@ func applyOpenAIThinkingDisable(body map[string]any, req StreamRequest, baseURL
delete(body, "reasoning_effort")
setRequestKnob(req, "thinking_disabled_provider_param", "enable_thinking")
case "reasoning_none":
if endpoint == modelchannel.OpenAIEndpointResponses {
if modelchannel.OpenAIEndpointShape(endpoint) == "responses" {
body["reasoning"] = map[string]any{"effort": "none"}
} else {
body["reasoning_effort"] = "none"