Files
ragflow/internal/entity/model_test.go
ghost b2053cc3c7 feat(go-models): add PPIO provider driver (#15099)
### What problem does this PR solve?

Closes #15089.

Adds PPIO support to the Go model-provider layer so PPIO instances can
be routed through the Go API server with the same OpenAI-compatible
chat, streaming, model listing, and connection-check flow used by other
SaaS providers.

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

## Summary

- Added a PPIO Go model driver.
- Added the PPIO provider catalog and default OpenAI-compatible API URL.
- Registered PPIO in the model factory.
- Added focused provider and provider-manager tests.

## What changed

- Implemented chat completions, SSE streaming, ListModels, and
CheckConnection for PPIO.
- Covered request shape, stream termination, reasoning fallback, model
listing, custom base URLs, safe transport setup, unsupported methods,
and provider config loading.
- Kept the provider catalog aligned with the existing RAGFlow PPIO
factory model set.
- Cleaned up pre-existing Go model package validation blockers so the
scoped provider tests can run normally with vet enabled.

## Why

The existing Python/provider catalog path includes PPIO, but the Go
model-provider layer did not have a PPIO driver, so the Go API server
could not instantiate or use PPIO as requested in #15089.
2026-05-22 11:52:18 +08:00

129 lines
3.7 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 entity
import (
"os"
"path/filepath"
modeldrivers "ragflow/internal/entity/models"
"testing"
)
func readPPIOProviderConfig(t *testing.T) []byte {
t.Helper()
for _, candidate := range []string{
filepath.Join("..", "..", "conf", "models", "ppio.json"),
filepath.Join("conf", "models", "ppio.json"),
} {
data, err := os.ReadFile(candidate)
if err == nil {
return data
}
}
t.Fatal("could not locate conf/models/ppio.json")
return nil
}
func TestPPIOProviderConfigLoadsIntoProviderManager(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "ppio.json"), readPPIOProviderConfig(t), 0o600); err != nil {
t.Fatalf("write ppio config: %v", err)
}
pm, err := NewProviderManager(dir)
if err != nil {
t.Fatalf("NewProviderManager: %v", err)
}
provider := pm.FindProvider("ppio")
if provider == nil {
t.Fatal("PPIO provider not found")
}
if provider.Name != "PPIO" {
t.Errorf("provider.Name=%q", provider.Name)
}
if provider.URL["default"] != "https://api.ppio.com/openai/v1" {
t.Errorf("default URL=%q", provider.URL["default"])
}
if provider.URL["us"] != "https://api.ppinfra.com/v3/openai" {
t.Errorf("us URL=%q", provider.URL["us"])
}
if provider.URLSuffix.Chat != "chat/completions" {
t.Errorf("chat suffix=%q", provider.URLSuffix.Chat)
}
if provider.URLSuffix.Models != "models" {
t.Errorf("models suffix=%q", provider.URLSuffix.Models)
}
if _, ok := provider.ModelDriver.(*modeldrivers.PPIOModel); !ok {
t.Fatalf("ModelDriver=%T, want *models.PPIOModel", provider.ModelDriver)
}
if provider.ModelDriver.Name() != "ppio" {
t.Errorf("ModelDriver.Name()=%q", provider.ModelDriver.Name())
}
if len(provider.Models) != 21 {
t.Fatalf("PPIO model count=%d, want 21", len(provider.Models))
}
for _, model := range provider.Models {
if !model.ModelTypeMap["chat"] {
t.Errorf("model %q missing chat type map", model.Name)
}
if model.Class == nil || *model.Class != "PPIO" {
t.Errorf("model %q class=%v", model.Name, model.Class)
}
}
models, err := pm.ListModels("PPIO")
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if len(models) != 21 {
t.Errorf("ListModels count=%d, want 21", len(models))
}
model, err := pm.GetModelByName("ppio", "deepseek/deepseek-r1")
if err != nil {
t.Fatalf("GetModelByName: %v", err)
}
if model.MaxTokens != 64000 {
t.Errorf("deepseek/deepseek-r1 max_tokens=%d", model.MaxTokens)
}
model, err = pm.GetModelByName("ppio", "deepseek/deepseek-v4-pro")
if err != nil {
t.Fatalf("GetModelByName v4 pro: %v", err)
}
if model.MaxTokens != 1048576 {
t.Errorf("deepseek/deepseek-v4-pro max_tokens=%d", model.MaxTokens)
}
model, err = pm.GetModelByName("ppio", "deepseek/deepseek-v4-flash")
if err != nil {
t.Fatalf("GetModelByName v4 flash: %v", err)
}
if model.MaxTokens != 1048576 {
t.Errorf("deepseek/deepseek-v4-flash max_tokens=%d", model.MaxTokens)
}
resp := pm.SearchByType("chat")
if resp.Code != 0 {
t.Fatalf("SearchByType code=%d message=%q", resp.Code, resp.Message)
}
if len(resp.Data) != 21 {
t.Errorf("SearchByType data count=%d, want 21", len(resp.Data))
}
}