feat(gemini): add Gemini thinking configuration support and metadata normalization

- Introduced logic to parse and apply `thinkingBudget` and `include_thoughts` configurations from metadata.
- Enhanced request handling to include normalized Gemini model metadata, preserving the original model identifier.
- Updated Gemini and Gemini-CLI executors to apply thinking configuration based on metadata overrides.
- Refactored handlers to support metadata extraction and cloning during request preparation.
This commit is contained in:
Luis Pater
2025-10-16 11:31:18 +08:00
parent 9c5ac2927a
commit ade279d1f2
4 changed files with 257 additions and 6 deletions

View File

@@ -133,20 +133,27 @@ func (h *BaseAPIHandler) GetContextWithCancel(handler interfaces.APIHandler, c *
// ExecuteWithAuthManager executes a non-streaming request via the core auth manager.
// This path is the only supported execution route.
func (h *BaseAPIHandler) ExecuteWithAuthManager(ctx context.Context, handlerType, modelName string, rawJSON []byte, alt string) ([]byte, *interfaces.ErrorMessage) {
providers := util.GetProviderName(modelName)
normalizedModel, metadata := normalizeModelMetadata(modelName)
providers := util.GetProviderName(normalizedModel)
if len(providers) == 0 {
return nil, &interfaces.ErrorMessage{StatusCode: http.StatusBadRequest, Error: fmt.Errorf("unknown provider for model %s", modelName)}
}
req := coreexecutor.Request{
Model: modelName,
Model: normalizedModel,
Payload: cloneBytes(rawJSON),
}
if cloned := cloneMetadata(metadata); cloned != nil {
req.Metadata = cloned
}
opts := coreexecutor.Options{
Stream: false,
Alt: alt,
OriginalRequest: cloneBytes(rawJSON),
SourceFormat: sdktranslator.FromString(handlerType),
}
if cloned := cloneMetadata(metadata); cloned != nil {
opts.Metadata = cloned
}
resp, err := h.AuthManager.Execute(ctx, providers, req, opts)
if err != nil {
return nil, &interfaces.ErrorMessage{StatusCode: http.StatusInternalServerError, Error: err}
@@ -157,20 +164,27 @@ func (h *BaseAPIHandler) ExecuteWithAuthManager(ctx context.Context, handlerType
// ExecuteCountWithAuthManager executes a non-streaming request via the core auth manager.
// This path is the only supported execution route.
func (h *BaseAPIHandler) ExecuteCountWithAuthManager(ctx context.Context, handlerType, modelName string, rawJSON []byte, alt string) ([]byte, *interfaces.ErrorMessage) {
providers := util.GetProviderName(modelName)
normalizedModel, metadata := normalizeModelMetadata(modelName)
providers := util.GetProviderName(normalizedModel)
if len(providers) == 0 {
return nil, &interfaces.ErrorMessage{StatusCode: http.StatusBadRequest, Error: fmt.Errorf("unknown provider for model %s", modelName)}
}
req := coreexecutor.Request{
Model: modelName,
Model: normalizedModel,
Payload: cloneBytes(rawJSON),
}
if cloned := cloneMetadata(metadata); cloned != nil {
req.Metadata = cloned
}
opts := coreexecutor.Options{
Stream: false,
Alt: alt,
OriginalRequest: cloneBytes(rawJSON),
SourceFormat: sdktranslator.FromString(handlerType),
}
if cloned := cloneMetadata(metadata); cloned != nil {
opts.Metadata = cloned
}
resp, err := h.AuthManager.ExecuteCount(ctx, providers, req, opts)
if err != nil {
return nil, &interfaces.ErrorMessage{StatusCode: http.StatusInternalServerError, Error: err}
@@ -181,7 +195,8 @@ func (h *BaseAPIHandler) ExecuteCountWithAuthManager(ctx context.Context, handle
// ExecuteStreamWithAuthManager executes a streaming request via the core auth manager.
// This path is the only supported execution route.
func (h *BaseAPIHandler) ExecuteStreamWithAuthManager(ctx context.Context, handlerType, modelName string, rawJSON []byte, alt string) (<-chan []byte, <-chan *interfaces.ErrorMessage) {
providers := util.GetProviderName(modelName)
normalizedModel, metadata := normalizeModelMetadata(modelName)
providers := util.GetProviderName(normalizedModel)
if len(providers) == 0 {
errChan := make(chan *interfaces.ErrorMessage, 1)
errChan <- &interfaces.ErrorMessage{StatusCode: http.StatusBadRequest, Error: fmt.Errorf("unknown provider for model %s", modelName)}
@@ -189,15 +204,21 @@ func (h *BaseAPIHandler) ExecuteStreamWithAuthManager(ctx context.Context, handl
return nil, errChan
}
req := coreexecutor.Request{
Model: modelName,
Model: normalizedModel,
Payload: cloneBytes(rawJSON),
}
if cloned := cloneMetadata(metadata); cloned != nil {
req.Metadata = cloned
}
opts := coreexecutor.Options{
Stream: true,
Alt: alt,
OriginalRequest: cloneBytes(rawJSON),
SourceFormat: sdktranslator.FromString(handlerType),
}
if cloned := cloneMetadata(metadata); cloned != nil {
opts.Metadata = cloned
}
chunks, err := h.AuthManager.ExecuteStream(ctx, providers, req, opts)
if err != nil {
errChan := make(chan *interfaces.ErrorMessage, 1)
@@ -232,6 +253,34 @@ func cloneBytes(src []byte) []byte {
return dst
}
func normalizeModelMetadata(modelName string) (string, map[string]any) {
baseModel, budget, include, matched := util.ParseGeminiThinkingSuffix(modelName)
if !matched {
return baseModel, nil
}
metadata := map[string]any{
util.GeminiOriginalModelMetadataKey: modelName,
}
if budget != nil {
metadata[util.GeminiThinkingBudgetMetadataKey] = *budget
}
if include != nil {
metadata[util.GeminiIncludeThoughtsMetadataKey] = *include
}
return baseModel, metadata
}
func cloneMetadata(src map[string]any) map[string]any {
if len(src) == 0 {
return nil
}
dst := make(map[string]any, len(src))
for k, v := range src {
dst[k] = v
}
return dst
}
// WriteErrorResponse writes an error message to the response writer using the HTTP status embedded in the message.
func (h *BaseAPIHandler) WriteErrorResponse(c *gin.Context, msg *interfaces.ErrorMessage) {
status := http.StatusInternalServerError