feat(ai-proxy): add support for OpenAI Fine-Tuning API (#2424)

This commit is contained in:
woody
2025-06-17 13:44:00 +08:00
committed by GitHub
parent c471bb2003
commit 926913f0e7
4 changed files with 121 additions and 53 deletions

View File

@@ -379,6 +379,33 @@ func getApiName(path string) provider.ApiName {
if strings.HasSuffix(path, "/v1/models") { if strings.HasSuffix(path, "/v1/models") {
return provider.ApiNameModels return provider.ApiNameModels
} }
if strings.HasSuffix(path, "/v1/fine_tuning/jobs") {
return provider.ApiNameFineTuningJobs
}
if util.RegRetrieveFineTuningJobPath.MatchString(path) {
return provider.ApiNameFineTuningRetrieveJob
}
if util.RegRetrieveFineTuningJobEventsPath.MatchString(path) {
return provider.PathOpenAIFineTuningJobEvents
}
if util.RegRetrieveFineTuningJobCheckpointsPath.MatchString(path) {
return provider.PathOpenAIFineTuningJobCheckpoints
}
if util.RegCancelFineTuningJobPath.MatchString(path) {
return provider.ApiNameFineTuningCancelJob
}
if util.RegResumeFineTuningJobPath.MatchString(path) {
return provider.ApiNameFineTuningResumeJob
}
if util.RegPauseFineTuningJobPath.MatchString(path) {
return provider.ApiNameFineTuningPauseJob
}
if util.RegFineTuningCheckpointPermissionPath.MatchString(path) {
return provider.ApiNameFineTuningCheckpointPermissions
}
if util.RegDeleteFineTuningCheckpointPermissionPath.MatchString(path) {
return provider.PathOpenAIFineDeleteTuningCheckpointPermission
}
// cohere style // cohere style
if strings.HasSuffix(path, "/v1/rerank") { if strings.HasSuffix(path, "/v1/rerank") {
return provider.ApiNameCohereV1Rerank return provider.ApiNameCohereV1Rerank

View File

@@ -26,24 +26,34 @@ func (m *openaiProviderInitializer) ValidateConfig(config *ProviderConfig) error
func (m *openaiProviderInitializer) DefaultCapabilities() map[string]string { func (m *openaiProviderInitializer) DefaultCapabilities() map[string]string {
return map[string]string{ return map[string]string{
string(ApiNameCompletion): PathOpenAICompletions, string(ApiNameCompletion): PathOpenAICompletions,
string(ApiNameChatCompletion): PathOpenAIChatCompletions, string(ApiNameChatCompletion): PathOpenAIChatCompletions,
string(ApiNameEmbeddings): PathOpenAIEmbeddings, string(ApiNameEmbeddings): PathOpenAIEmbeddings,
string(ApiNameImageGeneration): PathOpenAIImageGeneration, string(ApiNameImageGeneration): PathOpenAIImageGeneration,
string(ApiNameImageEdit): PathOpenAIImageEdit, string(ApiNameImageEdit): PathOpenAIImageEdit,
string(ApiNameImageVariation): PathOpenAIImageVariation, string(ApiNameImageVariation): PathOpenAIImageVariation,
string(ApiNameAudioSpeech): PathOpenAIAudioSpeech, string(ApiNameAudioSpeech): PathOpenAIAudioSpeech,
string(ApiNameModels): PathOpenAIModels, string(ApiNameModels): PathOpenAIModels,
string(ApiNameFiles): PathOpenAIFiles, string(ApiNameFiles): PathOpenAIFiles,
string(ApiNameRetrieveFile): PathOpenAIRetrieveFile, string(ApiNameRetrieveFile): PathOpenAIRetrieveFile,
string(ApiNameRetrieveFileContent): PathOpenAIRetrieveFileContent, string(ApiNameRetrieveFileContent): PathOpenAIRetrieveFileContent,
string(ApiNameBatches): PathOpenAIBatches, string(ApiNameBatches): PathOpenAIBatches,
string(ApiNameRetrieveBatch): PathOpenAIRetrieveBatch, string(ApiNameRetrieveBatch): PathOpenAIRetrieveBatch,
string(ApiNameCancelBatch): PathOpenAICancelBatch, string(ApiNameCancelBatch): PathOpenAICancelBatch,
string(ApiNameResponses): PathOpenAIResponses, string(ApiNameResponses): PathOpenAIResponses,
string(ApiNameFineTuningJobs): PathOpenAIFineTuningJobs,
string(ApiNameFineTuningRetrieveJob): PathOpenAIFineTuningRetrieveJob,
string(ApiNameFineTuningJobEvents): PathOpenAIFineTuningJobEvents,
string(ApiNameFineTuningJobCheckpoints): PathOpenAIFineTuningJobCheckpoints,
string(ApiNameFineTuningCancelJob): PathOpenAIFineTuningCancelJob,
string(ApiNameFineTuningResumeJob): PathOpenAIFineTuningResumeJob,
string(ApiNameFineTuningPauseJob): PathOpenAIFineTuningPauseJob,
string(ApiNameFineTuningCheckpointPermissions): PathOpenAIFineTuningCheckpointPermissions,
string(ApiNameDeleteFineTuningCheckpointPermission): PathOpenAIFineDeleteTuningCheckpointPermission,
} }
} }
// isDirectPath checks if the path is a known standard OpenAI interface path.
func isDirectPath(path string) bool { func isDirectPath(path string) bool {
return strings.HasSuffix(path, "/completions") || return strings.HasSuffix(path, "/completions") ||
strings.HasSuffix(path, "/embeddings") || strings.HasSuffix(path, "/embeddings") ||
@@ -52,7 +62,9 @@ func isDirectPath(path string) bool {
strings.HasSuffix(path, "/images/variations") || strings.HasSuffix(path, "/images/variations") ||
strings.HasSuffix(path, "/images/edits") || strings.HasSuffix(path, "/images/edits") ||
strings.HasSuffix(path, "/models") || strings.HasSuffix(path, "/models") ||
strings.HasSuffix(path, "/responses") strings.HasSuffix(path, "/responses") ||
strings.HasSuffix(path, "/fine_tuning/jobs") ||
strings.HasSuffix(path, "/fine_tuning/checkpoints")
} }
func (m *openaiProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) { func (m *openaiProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {

View File

@@ -27,42 +27,60 @@ const (
// ApiName 格式 {vendor}/{version}/{apitype} // ApiName 格式 {vendor}/{version}/{apitype}
// 表示遵循 厂商/版本/接口类型 的格式 // 表示遵循 厂商/版本/接口类型 的格式
// 目前openai是事实意义上的标准但是也有其他厂商存在其他任务的一些可能的标准比如cohere的rerank // 目前openai是事实意义上的标准但是也有其他厂商存在其他任务的一些可能的标准比如cohere的rerank
ApiNameCompletion ApiName = "openai/v1/completions" ApiNameCompletion ApiName = "openai/v1/completions"
ApiNameChatCompletion ApiName = "openai/v1/chatcompletions" ApiNameChatCompletion ApiName = "openai/v1/chatcompletions"
ApiNameEmbeddings ApiName = "openai/v1/embeddings" ApiNameEmbeddings ApiName = "openai/v1/embeddings"
ApiNameImageGeneration ApiName = "openai/v1/imagegeneration" ApiNameImageGeneration ApiName = "openai/v1/imagegeneration"
ApiNameImageEdit ApiName = "openai/v1/imageedit" ApiNameImageEdit ApiName = "openai/v1/imageedit"
ApiNameImageVariation ApiName = "openai/v1/imagevariation" ApiNameImageVariation ApiName = "openai/v1/imagevariation"
ApiNameAudioSpeech ApiName = "openai/v1/audiospeech" ApiNameAudioSpeech ApiName = "openai/v1/audiospeech"
ApiNameFiles ApiName = "openai/v1/files" ApiNameFiles ApiName = "openai/v1/files"
ApiNameRetrieveFile ApiName = "openai/v1/retrievefile" ApiNameRetrieveFile ApiName = "openai/v1/retrievefile"
ApiNameRetrieveFileContent ApiName = "openai/v1/retrievefilecontent" ApiNameRetrieveFileContent ApiName = "openai/v1/retrievefilecontent"
ApiNameBatches ApiName = "openai/v1/batches" ApiNameBatches ApiName = "openai/v1/batches"
ApiNameRetrieveBatch ApiName = "openai/v1/retrievebatch" ApiNameRetrieveBatch ApiName = "openai/v1/retrievebatch"
ApiNameCancelBatch ApiName = "openai/v1/cancelbatch" ApiNameCancelBatch ApiName = "openai/v1/cancelbatch"
ApiNameModels ApiName = "openai/v1/models" ApiNameModels ApiName = "openai/v1/models"
ApiNameResponses ApiName = "openai/v1/responses" ApiNameResponses ApiName = "openai/v1/responses"
ApiNameFineTuningJobs ApiName = "openai/v1/fine-tuningjobs"
ApiNameFineTuningRetrieveJob ApiName = "openai/v1/retrievefine-tuningjob"
ApiNameFineTuningJobEvents ApiName = "openai/v1/fine-tuningjobsevents"
ApiNameFineTuningJobCheckpoints ApiName = "openai/v1/fine-tuningjobcheckpoints"
ApiNameFineTuningCancelJob ApiName = "openai/v1/cancelfine-tuningjob"
ApiNameFineTuningResumeJob ApiName = "openai/v1/resumefine-tuningjob"
ApiNameFineTuningPauseJob ApiName = "openai/v1/pausefine-tuningjob"
ApiNameFineTuningCheckpointPermissions ApiName = "openai/v1/fine-tuningjobcheckpointpermissions"
ApiNameDeleteFineTuningCheckpointPermission ApiName = "openai/v1/deletefine-tuningjobcheckpointpermission"
PathOpenAICompletions = "/v1/completions" PathOpenAICompletions = "/v1/completions"
PathOpenAIChatCompletions = "/v1/chat/completions" PathOpenAIChatCompletions = "/v1/chat/completions"
PathOpenAIEmbeddings = "/v1/embeddings" PathOpenAIEmbeddings = "/v1/embeddings"
PathOpenAIFiles = "/v1/files" PathOpenAIFiles = "/v1/files"
PathOpenAIRetrieveFile = "/v1/files/{file_id}" PathOpenAIRetrieveFile = "/v1/files/{file_id}"
PathOpenAIRetrieveFileContent = "/v1/files/{file_id}/content" PathOpenAIRetrieveFileContent = "/v1/files/{file_id}/content"
PathOpenAIBatches = "/v1/batches" PathOpenAIBatches = "/v1/batches"
PathOpenAIRetrieveBatch = "/v1/batches/{batch_id}" PathOpenAIRetrieveBatch = "/v1/batches/{batch_id}"
PathOpenAICancelBatch = "/v1/batches/{batch_id}/cancel" PathOpenAICancelBatch = "/v1/batches/{batch_id}/cancel"
PathOpenAIModels = "/v1/models" PathOpenAIModels = "/v1/models"
PathOpenAIImageGeneration = "/v1/images/generations" PathOpenAIImageGeneration = "/v1/images/generations"
PathOpenAIImageEdit = "/v1/images/edits" PathOpenAIImageEdit = "/v1/images/edits"
PathOpenAIImageVariation = "/v1/images/variations" PathOpenAIImageVariation = "/v1/images/variations"
PathOpenAIAudioSpeech = "/v1/audio/speech" PathOpenAIAudioSpeech = "/v1/audio/speech"
PathOpenAIResponses = "/v1/responses" PathOpenAIResponses = "/v1/responses"
PathOpenAIFineTuningJobs = "/v1/fine_tuning/jobs"
PathOpenAIFineTuningRetrieveJob = "/v1/fine_tuning/jobs/{fine_tuning_job_id}"
PathOpenAIFineTuningJobEvents = "/v1/fine_tuning/jobs/{fine_tuning_job_id}/events"
PathOpenAIFineTuningJobCheckpoints = "/v1/fine_tuning/jobs/{fine_tuning_job_id}/checkpoints"
PathOpenAIFineTuningCancelJob = "/v1/fine_tuning/jobs/{fine_tuning_job_id}/cancel"
PathOpenAIFineTuningResumeJob = "/v1/fine_tuning/jobs/{fine_tuning_job_id}/resume"
PathOpenAIFineTuningPauseJob = "/v1/fine_tuning/jobs/{fine_tuning_job_id}/pause"
PathOpenAIFineTuningCheckpointPermissions = "/v1/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions"
PathOpenAIFineDeleteTuningCheckpointPermission = "/v1/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions/{permission_id}"
// TODO: 以下是一些非标准的API名称需要进一步确认是否支持 // TODO: 以下是一些非标准的API名称需要进一步确认是否支持
ApiNameCohereV1Rerank ApiName = "cohere/v1/rerank" ApiNameCohereV1Rerank ApiName = "cohere/v1/rerank"
ApiNameQwenAsyncAIGC ApiName = "api/v1/services/aigc" ApiNameQwenAsyncAIGC ApiName = "api/v1/services/aigc"
ApiNameQwenAsyncTask ApiName = "api/v1/tasks/" ApiNameQwenAsyncTask ApiName = "api/v1/tasks/"
providerTypeMoonshot = "moonshot" providerTypeMoonshot = "moonshot"
providerTypeAzure = "azure" providerTypeAzure = "azure"
@@ -842,11 +860,14 @@ func (c *ProviderConfig) DefaultTransformResponseHeaders(ctx wrapper.HttpContext
func (c *ProviderConfig) needToProcessRequestBody(apiName ApiName) bool { func (c *ProviderConfig) needToProcessRequestBody(apiName ApiName) bool {
switch apiName { switch apiName {
case ApiNameChatCompletion, case ApiNameChatCompletion,
ApiNameCompletion,
ApiNameEmbeddings, ApiNameEmbeddings,
ApiNameImageGeneration, ApiNameImageGeneration,
ApiNameImageEdit, ApiNameImageEdit,
ApiNameImageVariation, ApiNameImageVariation,
ApiNameAudioSpeech: ApiNameAudioSpeech,
ApiNameFineTuningJobs,
ApiNameResponses:
return true return true
} }
return false return false

View File

@@ -17,10 +17,18 @@ const (
) )
var ( var (
RegRetrieveBatchPath = regexp.MustCompile(`^.*/v1/batches/(?P<batch_id>[^/]+)$`) RegRetrieveBatchPath = regexp.MustCompile(`^.*/v1/batches/(?P<batch_id>[^/]+)$`)
RegCancelBatchPath = regexp.MustCompile(`^.*/v1/batches/(?P<batch_id>[^/]+)/cancel$`) RegCancelBatchPath = regexp.MustCompile(`^.*/v1/batches/(?P<batch_id>[^/]+)/cancel$`)
RegRetrieveFilePath = regexp.MustCompile(`^.*/v1/files/(?P<file_id>[^/]+)$`) RegRetrieveFilePath = regexp.MustCompile(`^.*/v1/files/(?P<file_id>[^/]+)$`)
RegRetrieveFileContentPath = regexp.MustCompile(`^.*/v1/files/(?P<file_id>[^/]+)/content$`) RegRetrieveFileContentPath = regexp.MustCompile(`^.*/v1/files/(?P<file_id>[^/]+)/content$`)
RegRetrieveFineTuningJobPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)$`)
RegRetrieveFineTuningJobEventsPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)/events$`)
RegRetrieveFineTuningJobCheckpointsPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)/checkpoints$`)
RegCancelFineTuningJobPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)/cancel$`)
RegResumeFineTuningJobPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)/resume$`)
RegPauseFineTuningJobPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)/pause$`)
RegFineTuningCheckpointPermissionPath = regexp.MustCompile(`^.*/v1/fine_tuning/checkpoints/(?P<fine_tuned_model_checkpoint>[^/]+)/permissions$`)
RegDeleteFineTuningCheckpointPermissionPath = regexp.MustCompile(`^.*/v1/fine_tuning/checkpoints/(?P<fine_tuned_model_checkpoint>[^/]+)/permissions/(?P<permission_id>[^/]+)$`)
) )
type ErrorHandlerFunc func(statusCodeDetails string, err error) error type ErrorHandlerFunc func(statusCodeDetails string, err error) error