mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
feat: connection test of provider
This commit is contained in:
@@ -35,6 +35,7 @@ func (h *ProvidersHandler) Register(e *echo.Echo) {
|
||||
group.PUT("/:id", h.Update)
|
||||
group.DELETE("/:id", h.Delete)
|
||||
group.GET("/count", h.Count)
|
||||
group.POST("/:id/test", h.Test)
|
||||
}
|
||||
|
||||
// Create godoc
|
||||
@@ -252,3 +253,32 @@ func (h *ProvidersHandler) Count(c echo.Context) error {
|
||||
|
||||
return c.JSON(http.StatusOK, providers.CountResponse{Count: count})
|
||||
}
|
||||
|
||||
// Test godoc
|
||||
// @Summary Test provider connectivity
|
||||
// @Description Probe a provider's base URL to check reachability, supported client types, and embedding support
|
||||
// @Tags providers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Provider ID (UUID)"
|
||||
// @Success 200 {object} providers.TestResponse
|
||||
// @Failure 400 {object} ErrorResponse
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Failure 500 {object} ErrorResponse
|
||||
// @Router /providers/{id}/test [post]
|
||||
func (h *ProvidersHandler) Test(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "id is required")
|
||||
}
|
||||
|
||||
resp, err := h.service.Test(c.Request().Context(), id)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "invalid") {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
return echo.NewHTTPError(http.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/memohai/memoh/internal/db"
|
||||
"github.com/memohai/memoh/internal/db/sqlc"
|
||||
@@ -158,6 +163,186 @@ func (s *Service) Count(ctx context.Context) (int64, error) {
|
||||
return count, nil
|
||||
}
|
||||
|
||||
const probeTimeout = 5 * time.Second
|
||||
|
||||
// Test probes the provider's base URL to check connectivity, supported
|
||||
// client types, and embedding support. All probes run concurrently.
|
||||
func (s *Service) Test(ctx context.Context, id string) (TestResponse, error) {
|
||||
providerID, err := db.ParseUUID(id)
|
||||
if err != nil {
|
||||
return TestResponse{}, err
|
||||
}
|
||||
|
||||
provider, err := s.queries.GetLlmProviderByID(ctx, providerID)
|
||||
if err != nil {
|
||||
return TestResponse{}, fmt.Errorf("get provider: %w", err)
|
||||
}
|
||||
|
||||
baseURL := strings.TrimRight(provider.BaseUrl, "/")
|
||||
apiKey := provider.ApiKey
|
||||
|
||||
resp := TestResponse{Checks: make(map[string]CheckResult, 5)}
|
||||
|
||||
// Connectivity check
|
||||
start := time.Now()
|
||||
reachable, reachMsg := probeReachable(ctx, baseURL)
|
||||
resp.Reachable = reachable
|
||||
resp.LatencyMs = time.Since(start).Milliseconds()
|
||||
if !reachable {
|
||||
resp.Message = reachMsg
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type namedResult struct {
|
||||
name string
|
||||
result CheckResult
|
||||
}
|
||||
|
||||
probes := []struct {
|
||||
name string
|
||||
fn func() CheckResult
|
||||
}{
|
||||
{"openai-completions", func() CheckResult {
|
||||
return probeOpenAICompletions(ctx, baseURL, apiKey)
|
||||
}},
|
||||
{"openai-responses", func() CheckResult {
|
||||
return probeOpenAIResponses(ctx, baseURL, apiKey)
|
||||
}},
|
||||
{"anthropic-messages", func() CheckResult {
|
||||
return probeAnthropicMessages(ctx, baseURL, apiKey)
|
||||
}},
|
||||
{"google-generative-ai", func() CheckResult {
|
||||
return probeGoogleGenerativeAI(ctx, baseURL, apiKey)
|
||||
}},
|
||||
{"embedding", func() CheckResult {
|
||||
return probeEmbedding(ctx, baseURL, apiKey)
|
||||
}},
|
||||
}
|
||||
|
||||
results := make([]namedResult, len(probes))
|
||||
var wg sync.WaitGroup
|
||||
for i, p := range probes {
|
||||
wg.Add(1)
|
||||
go func(idx int, name string, fn func() CheckResult) {
|
||||
defer wg.Done()
|
||||
results[idx] = namedResult{name: name, result: fn()}
|
||||
}(i, p.name, p.fn)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for _, nr := range results {
|
||||
resp.Checks[nr.name] = nr.result
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func probeReachable(ctx context.Context, baseURL string) (bool, string) {
|
||||
ctx, cancel := context.WithTimeout(ctx, probeTimeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
httpResp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
io.Copy(io.Discard, httpResp.Body)
|
||||
httpResp.Body.Close()
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func probeOpenAICompletions(ctx context.Context, baseURL, apiKey string) CheckResult {
|
||||
body := `{"model":"probe-test","messages":[{"role":"user","content":"hi"}],"max_tokens":1}`
|
||||
return probeEndpoint(ctx, http.MethodPost, baseURL+"/chat/completions",
|
||||
map[string]string{
|
||||
"Authorization": "Bearer " + apiKey,
|
||||
"Content-Type": "application/json",
|
||||
}, body)
|
||||
}
|
||||
|
||||
func probeOpenAIResponses(ctx context.Context, baseURL, apiKey string) CheckResult {
|
||||
body := `{"model":"probe-test","input":"hi","max_output_tokens":1}`
|
||||
return probeEndpoint(ctx, http.MethodPost, baseURL+"/responses",
|
||||
map[string]string{
|
||||
"Authorization": "Bearer " + apiKey,
|
||||
"Content-Type": "application/json",
|
||||
}, body)
|
||||
}
|
||||
|
||||
func probeAnthropicMessages(ctx context.Context, baseURL, apiKey string) CheckResult {
|
||||
body := `{"model":"probe-test","messages":[{"role":"user","content":"hi"}],"max_tokens":1}`
|
||||
return probeEndpoint(ctx, http.MethodPost, baseURL+"/messages",
|
||||
map[string]string{
|
||||
"x-api-key": apiKey,
|
||||
"anthropic-version": "2023-06-01",
|
||||
"Content-Type": "application/json",
|
||||
}, body)
|
||||
}
|
||||
|
||||
func probeGoogleGenerativeAI(ctx context.Context, baseURL, apiKey string) CheckResult {
|
||||
return probeEndpoint(ctx, http.MethodGet, baseURL+"/models",
|
||||
map[string]string{
|
||||
"x-goog-api-key": apiKey,
|
||||
}, "")
|
||||
}
|
||||
|
||||
func probeEmbedding(ctx context.Context, baseURL, apiKey string) CheckResult {
|
||||
body := `{"model":"probe-test","input":"hello"}`
|
||||
return probeEndpoint(ctx, http.MethodPost, baseURL+"/embeddings",
|
||||
map[string]string{
|
||||
"Authorization": "Bearer " + apiKey,
|
||||
"Content-Type": "application/json",
|
||||
}, body)
|
||||
}
|
||||
|
||||
func probeEndpoint(ctx context.Context, method, url string, headers map[string]string, body string) CheckResult {
|
||||
ctx, cancel := context.WithTimeout(ctx, probeTimeout)
|
||||
defer cancel()
|
||||
|
||||
var bodyReader io.Reader
|
||||
if body != "" {
|
||||
bodyReader = bytes.NewBufferString(body)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
|
||||
if err != nil {
|
||||
return CheckResult{Status: CheckStatusError, Message: err.Error()}
|
||||
}
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
latency := time.Since(start).Milliseconds()
|
||||
if err != nil {
|
||||
return CheckResult{Status: CheckStatusError, LatencyMs: latency, Message: err.Error()}
|
||||
}
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
|
||||
return classifyResponse(resp.StatusCode, latency)
|
||||
}
|
||||
|
||||
func classifyResponse(statusCode int, latencyMs int64) CheckResult {
|
||||
r := CheckResult{StatusCode: statusCode, LatencyMs: latencyMs}
|
||||
switch {
|
||||
case statusCode >= 200 && statusCode <= 299,
|
||||
statusCode == 400, statusCode == 422, statusCode == 429:
|
||||
r.Status = CheckStatusSupported
|
||||
case statusCode == 401 || statusCode == 403:
|
||||
r.Status = CheckStatusAuthError
|
||||
case statusCode == 404 || statusCode == 405:
|
||||
r.Status = CheckStatusUnsupported
|
||||
default:
|
||||
r.Status = CheckStatusError
|
||||
r.Message = fmt.Sprintf("unexpected status %d", statusCode)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// toGetResponse converts a database provider to a response
|
||||
func (s *Service) toGetResponse(provider sqlc.LlmProvider) GetResponse {
|
||||
var metadata map[string]any
|
||||
|
||||
@@ -40,16 +40,28 @@ type CountResponse struct {
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
|
||||
// TestRequest represents a request to test provider connection
|
||||
type TestRequest struct {
|
||||
BaseURL string `json:"base_url" validate:"required,url"`
|
||||
APIKey string `json:"api_key"`
|
||||
Model string `json:"model"` // optional test model
|
||||
// CheckStatus represents the result status of a single probe check.
|
||||
type CheckStatus string
|
||||
|
||||
const (
|
||||
CheckStatusSupported CheckStatus = "supported"
|
||||
CheckStatusAuthError CheckStatus = "auth_error"
|
||||
CheckStatusUnsupported CheckStatus = "unsupported"
|
||||
CheckStatusError CheckStatus = "error"
|
||||
)
|
||||
|
||||
// CheckResult holds the outcome of probing a single endpoint.
|
||||
type CheckResult struct {
|
||||
Status CheckStatus `json:"status"`
|
||||
StatusCode int `json:"status_code,omitempty"`
|
||||
LatencyMs int64 `json:"latency_ms,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// TestResponse represents the result of testing a provider
|
||||
// TestResponse is returned by POST /providers/:id/test.
|
||||
type TestResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Latency int64 `json:"latency_ms,omitempty"` // latency in milliseconds
|
||||
Reachable bool `json:"reachable"`
|
||||
LatencyMs int64 `json:"latency_ms,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Checks map[string]CheckResult `json:"checks"`
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -733,6 +733,15 @@ export type ModelsUpdateRequest = {
|
||||
type?: ModelsModelType;
|
||||
};
|
||||
|
||||
export type ProvidersCheckResult = {
|
||||
latency_ms?: number;
|
||||
message?: string;
|
||||
status?: ProvidersCheckStatus;
|
||||
status_code?: number;
|
||||
};
|
||||
|
||||
export type ProvidersCheckStatus = 'supported' | 'auth_error' | 'unsupported' | 'error';
|
||||
|
||||
export type ProvidersCountResponse = {
|
||||
count?: number;
|
||||
};
|
||||
@@ -761,6 +770,15 @@ export type ProvidersGetResponse = {
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type ProvidersTestResponse = {
|
||||
checks?: {
|
||||
[key: string]: ProvidersCheckResult;
|
||||
};
|
||||
latency_ms?: number;
|
||||
message?: string;
|
||||
reachable?: boolean;
|
||||
};
|
||||
|
||||
export type ProvidersUpdateRequest = {
|
||||
api_key?: string;
|
||||
base_url?: string;
|
||||
@@ -4274,6 +4292,44 @@ export type GetProvidersByIdModelsResponses = {
|
||||
|
||||
export type GetProvidersByIdModelsResponse = GetProvidersByIdModelsResponses[keyof GetProvidersByIdModelsResponses];
|
||||
|
||||
export type PostProvidersByIdTestData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Provider ID (UUID)
|
||||
*/
|
||||
id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/providers/{id}/test';
|
||||
};
|
||||
|
||||
export type PostProvidersByIdTestErrors = {
|
||||
/**
|
||||
* Bad Request
|
||||
*/
|
||||
400: HandlersErrorResponse;
|
||||
/**
|
||||
* Not Found
|
||||
*/
|
||||
404: HandlersErrorResponse;
|
||||
/**
|
||||
* Internal Server Error
|
||||
*/
|
||||
500: HandlersErrorResponse;
|
||||
};
|
||||
|
||||
export type PostProvidersByIdTestError = PostProvidersByIdTestErrors[keyof PostProvidersByIdTestErrors];
|
||||
|
||||
export type PostProvidersByIdTestResponses = {
|
||||
/**
|
||||
* OK
|
||||
*/
|
||||
200: ProvidersTestResponse;
|
||||
};
|
||||
|
||||
export type PostProvidersByIdTestResponse = PostProvidersByIdTestResponses[keyof PostProvidersByIdTestResponses];
|
||||
|
||||
export type GetSearchProvidersData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
|
||||
@@ -171,7 +171,16 @@
|
||||
"deleteConfirm": "Are you sure you want to delete this provider?",
|
||||
"saveChanges": "Save Changes",
|
||||
"emptyTitle": "No Providers",
|
||||
"emptyDescription": "Add a model provider first to configure models"
|
||||
"emptyDescription": "Add a model provider first to configure models",
|
||||
"testConnection": "Test Connection",
|
||||
"reachable": "Reachable",
|
||||
"unreachable": "Unreachable",
|
||||
"supported": "Supported",
|
||||
"authError": "Auth Error",
|
||||
"unsupported": "Unsupported",
|
||||
"error": "Error",
|
||||
"embedding": "Embedding",
|
||||
"testFailed": "Test failed"
|
||||
},
|
||||
"searchProvider": {
|
||||
"title": "Search Providers",
|
||||
|
||||
@@ -167,7 +167,16 @@
|
||||
"deleteConfirm": "确定要删除这个服务商吗?",
|
||||
"saveChanges": "保存修改",
|
||||
"emptyTitle": "暂无服务商",
|
||||
"emptyDescription": "请先添加模型服务商,才能配置模型"
|
||||
"emptyDescription": "请先添加模型服务商,才能配置模型",
|
||||
"testConnection": "测试连接",
|
||||
"reachable": "可连接",
|
||||
"unreachable": "不可连接",
|
||||
"supported": "支持",
|
||||
"authError": "认证失败",
|
||||
"unsupported": "不支持",
|
||||
"error": "错误",
|
||||
"embedding": "Embedding",
|
||||
"testFailed": "测试失败"
|
||||
},
|
||||
"searchProvider": {
|
||||
"title": "搜索提供方",
|
||||
|
||||
@@ -62,26 +62,86 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="flex justify-end mt-4 gap-4">
|
||||
<ConfirmPopover
|
||||
:message="$t('provider.deleteConfirm')"
|
||||
:loading="deleteLoading"
|
||||
@confirm="$emit('delete')"
|
||||
>
|
||||
<template #trigger>
|
||||
<Button variant="outline">
|
||||
<FontAwesomeIcon :icon="['far', 'trash-can']" />
|
||||
</Button>
|
||||
</template>
|
||||
</ConfirmPopover>
|
||||
|
||||
<section class="flex justify-between items-center mt-4">
|
||||
<Button
|
||||
type="submit"
|
||||
:disabled="!hasChanges || !form.meta.value.valid"
|
||||
type="button"
|
||||
variant="outline"
|
||||
:disabled="testLoading || !props.provider?.id"
|
||||
@click="runTest"
|
||||
>
|
||||
<Spinner v-if="editLoading" />
|
||||
{{ $t('provider.saveChanges') }}
|
||||
<Spinner v-if="testLoading" />
|
||||
{{ $t('provider.testConnection') }}
|
||||
</Button>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<ConfirmPopover
|
||||
:message="$t('provider.deleteConfirm')"
|
||||
:loading="deleteLoading"
|
||||
@confirm="$emit('delete')"
|
||||
>
|
||||
<template #trigger>
|
||||
<Button variant="outline">
|
||||
<FontAwesomeIcon :icon="['far', 'trash-can']" />
|
||||
</Button>
|
||||
</template>
|
||||
</ConfirmPopover>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
:disabled="!hasChanges || !form.meta.value.valid"
|
||||
>
|
||||
<Spinner v-if="editLoading" />
|
||||
{{ $t('provider.saveChanges') }}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="testResult"
|
||||
class="mt-4 rounded-lg border p-4 space-y-3 text-sm"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="inline-block size-2 rounded-full"
|
||||
:class="testResult.reachable ? 'bg-green-500' : 'bg-red-500'"
|
||||
/>
|
||||
<span class="font-medium">
|
||||
{{ testResult.reachable ? $t('provider.reachable') : $t('provider.unreachable') }}
|
||||
</span>
|
||||
<span
|
||||
v-if="testResult.latency_ms"
|
||||
class="text-muted-foreground"
|
||||
>
|
||||
{{ testResult.latency_ms }}ms
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<template v-if="testResult.reachable && testResult.checks">
|
||||
<div
|
||||
v-for="key in clientTypeKeys"
|
||||
:key="key"
|
||||
class="flex items-center justify-between"
|
||||
>
|
||||
<span>{{ clientTypeLabel(key) }}</span>
|
||||
<Badge :variant="statusVariant(testResult.checks[key]?.status)">
|
||||
{{ statusText(testResult.checks[key]?.status) }}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<span>{{ $t('provider.embedding') }}</span>
|
||||
<Badge :variant="statusVariant(testResult.checks['embedding']?.status)">
|
||||
{{ statusText(testResult.checks['embedding']?.status) }}
|
||||
</Badge>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-if="testError"
|
||||
class="text-destructive text-xs"
|
||||
>
|
||||
{{ testError }}
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</template>
|
||||
@@ -90,17 +150,23 @@
|
||||
import {
|
||||
Input,
|
||||
Button,
|
||||
Badge,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
Spinner,
|
||||
} from '@memoh/ui'
|
||||
import ConfirmPopover from '@/components/confirm-popover/index.vue'
|
||||
import { computed, watch } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import z from 'zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import type { ProvidersGetResponse } from '@memoh/sdk'
|
||||
import { postProvidersByIdTest } from '@memoh/sdk'
|
||||
import type { ProvidersGetResponse, ProvidersTestResponse, ProvidersCheckStatus } from '@memoh/sdk'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { CLIENT_TYPE_META } from '@/constants/client-types'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
provider: Partial<ProvidersGetResponse> | undefined
|
||||
@@ -113,6 +179,54 @@ const emit = defineEmits<{
|
||||
delete: []
|
||||
}>()
|
||||
|
||||
const testLoading = ref(false)
|
||||
const testResult = ref<ProvidersTestResponse | null>(null)
|
||||
const testError = ref('')
|
||||
|
||||
const clientTypeKeys = ['openai-completions', 'openai-responses', 'anthropic-messages', 'google-generative-ai']
|
||||
|
||||
function clientTypeLabel(key: string): string {
|
||||
return CLIENT_TYPE_META[key]?.label ?? key
|
||||
}
|
||||
|
||||
function statusVariant(status?: ProvidersCheckStatus): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||
switch (status) {
|
||||
case 'supported': return 'default'
|
||||
case 'auth_error': return 'secondary'
|
||||
case 'unsupported': return 'outline'
|
||||
case 'error': return 'destructive'
|
||||
default: return 'outline'
|
||||
}
|
||||
}
|
||||
|
||||
function statusText(status?: ProvidersCheckStatus): string {
|
||||
switch (status) {
|
||||
case 'supported': return t('provider.supported')
|
||||
case 'auth_error': return t('provider.authError')
|
||||
case 'unsupported': return t('provider.unsupported')
|
||||
case 'error': return t('provider.error')
|
||||
default: return '-'
|
||||
}
|
||||
}
|
||||
|
||||
async function runTest() {
|
||||
if (!props.provider?.id) return
|
||||
testLoading.value = true
|
||||
testResult.value = null
|
||||
testError.value = ''
|
||||
try {
|
||||
const { data } = await postProvidersByIdTest({
|
||||
path: { id: props.provider.id },
|
||||
throwOnError: true,
|
||||
})
|
||||
testResult.value = data ?? null
|
||||
} catch (err: unknown) {
|
||||
testError.value = err instanceof Error ? err.message : t('provider.testFailed')
|
||||
} finally {
|
||||
testLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const providerSchema = toTypedSchema(z.object({
|
||||
name: z.string().min(1),
|
||||
base_url: z.string().min(1),
|
||||
|
||||
+102
@@ -4055,6 +4055,56 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/providers/{id}/test": {
|
||||
"post": {
|
||||
"description": "Probe a provider's base URL to check reachability, supported client types, and embedding support",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"providers"
|
||||
],
|
||||
"summary": "Test provider connectivity",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Provider ID (UUID)",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/providers.TestResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/search-providers": {
|
||||
"get": {
|
||||
"description": "List configured search providers",
|
||||
@@ -6602,6 +6652,38 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"providers.CheckResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"latency_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/providers.CheckStatus"
|
||||
},
|
||||
"status_code": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"providers.CheckStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"supported",
|
||||
"auth_error",
|
||||
"unsupported",
|
||||
"error"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"CheckStatusSupported",
|
||||
"CheckStatusAuthError",
|
||||
"CheckStatusUnsupported",
|
||||
"CheckStatusError"
|
||||
]
|
||||
},
|
||||
"providers.CountResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6660,6 +6742,26 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"providers.TestResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"checks": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/providers.CheckResult"
|
||||
}
|
||||
},
|
||||
"latency_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"reachable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"providers.UpdateRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -4046,6 +4046,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/providers/{id}/test": {
|
||||
"post": {
|
||||
"description": "Probe a provider's base URL to check reachability, supported client types, and embedding support",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"providers"
|
||||
],
|
||||
"summary": "Test provider connectivity",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Provider ID (UUID)",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/providers.TestResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/search-providers": {
|
||||
"get": {
|
||||
"description": "List configured search providers",
|
||||
@@ -6593,6 +6643,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"providers.CheckResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"latency_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/providers.CheckStatus"
|
||||
},
|
||||
"status_code": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"providers.CheckStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"supported",
|
||||
"auth_error",
|
||||
"unsupported",
|
||||
"error"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"CheckStatusSupported",
|
||||
"CheckStatusAuthError",
|
||||
"CheckStatusUnsupported",
|
||||
"CheckStatusError"
|
||||
]
|
||||
},
|
||||
"providers.CountResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6651,6 +6733,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"providers.TestResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"checks": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/providers.CheckResult"
|
||||
}
|
||||
},
|
||||
"latency_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"reachable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"providers.UpdateRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1220,6 +1220,29 @@ definitions:
|
||||
type:
|
||||
$ref: '#/definitions/models.ModelType'
|
||||
type: object
|
||||
providers.CheckResult:
|
||||
properties:
|
||||
latency_ms:
|
||||
type: integer
|
||||
message:
|
||||
type: string
|
||||
status:
|
||||
$ref: '#/definitions/providers.CheckStatus'
|
||||
status_code:
|
||||
type: integer
|
||||
type: object
|
||||
providers.CheckStatus:
|
||||
enum:
|
||||
- supported
|
||||
- auth_error
|
||||
- unsupported
|
||||
- error
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- CheckStatusSupported
|
||||
- CheckStatusAuthError
|
||||
- CheckStatusUnsupported
|
||||
- CheckStatusError
|
||||
providers.CountResponse:
|
||||
properties:
|
||||
count:
|
||||
@@ -1259,6 +1282,19 @@ definitions:
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
providers.TestResponse:
|
||||
properties:
|
||||
checks:
|
||||
additionalProperties:
|
||||
$ref: '#/definitions/providers.CheckResult'
|
||||
type: object
|
||||
latency_ms:
|
||||
type: integer
|
||||
message:
|
||||
type: string
|
||||
reachable:
|
||||
type: boolean
|
||||
type: object
|
||||
providers.UpdateRequest:
|
||||
properties:
|
||||
api_key:
|
||||
@@ -4197,6 +4233,40 @@ paths:
|
||||
summary: List provider models
|
||||
tags:
|
||||
- providers
|
||||
/providers/{id}/test:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Probe a provider's base URL to check reachability, supported client
|
||||
types, and embedding support
|
||||
parameters:
|
||||
- description: Provider ID (UUID)
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/providers.TestResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ErrorResponse'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.ErrorResponse'
|
||||
summary: Test provider connectivity
|
||||
tags:
|
||||
- providers
|
||||
/providers/count:
|
||||
get:
|
||||
consumes:
|
||||
|
||||
Reference in New Issue
Block a user