iop/apps/node/internal/adapters/ollama/provider.go
toki fef1f7a9dc feat(liveness): provider 실행 stall 관측을 구현한다
Node의 provider progress 기반 stall timeout, watchdog fencing과 bounded health probe evidence를 실행 경로에 반영한다. Edge-Node 계약과 구현 스펙, 테스트 및 Milestone 완료 evidence를 현재 상태와 맞춘다.
2026-08-05 09:45:14 +09:00

81 lines
2.4 KiB
Go

package ollama
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
runtime "iop/packages/go/execution"
)
func (o *Ollama) ProbeProvider(ctx context.Context, target string) (runtime.ProviderProbeResult, error) {
probeCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
targets, err := o.fetchTargets(probeCtx)
result := runtime.ProviderProbeResult{
AdapterName: Name,
InstanceKey: o.instanceName,
Target: target,
}
if err != nil {
// Endpoint construction, request/network, non-success HTTP, and decode
// failures are inconclusive: they cannot prove the exact target absent,
// so the underlying error is surfaced instead of manufacturing
// unavailable. Only a valid response that positively reports the exact
// target absent remains StatusUnavailable.
result.Status = runtime.ProviderStatusUnknown
result.Detail = err.Error()
return result, err
}
result.Targets = targets
if target == "" {
result.Status = runtime.NormalizeProviderStatus(runtime.ProviderStatusAvailable)
return result, nil
}
for _, candidate := range targets {
if candidate == target || (!strings.Contains(target, ":") && candidate == target+":latest") {
result.Status = runtime.NormalizeProviderStatus(runtime.ProviderStatusAvailable)
return result, nil
}
}
result.Status = runtime.NormalizeProviderStatus(runtime.ProviderStatusUnavailable)
result.Detail = fmt.Sprintf("target model %q not found in provider models", target)
return result, nil
}
func (o *Ollama) fetchTargets(ctx context.Context) ([]string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, joinURL(o.baseURL, "/api/tags"), nil)
if err != nil {
return nil, fmt.Errorf("build request: %w", err)
}
resp, err := o.client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("status code %d", resp.StatusCode)
}
var tags ollamaTagsResponse
if err := json.NewDecoder(resp.Body).Decode(&tags); err != nil {
return nil, fmt.Errorf("decode response: %w", err)
}
out := make([]string, 0, len(tags.Models))
for _, model := range tags.Models {
if model.Name != "" {
out = append(out, model.Name)
}
}
return out, nil
}
type ollamaTagsResponse struct {
Models []struct {
Name string `json:"name"`
} `json:"models"`
}