update-1.6.2
ci / test (services/knowledge) (push) Successful in 47s
ci / test (services/agent) (push) Successful in 55s
ci / test (platform/neuroforge) (push) Successful in 56s
ci / docker-build (push) Failing after 1m10s
ci / test (services/control) (push) Successful in 1m26s
release-tag / release-image (push) Successful in 6m43s
ci / test (services/knowledge) (push) Successful in 47s
ci / test (services/agent) (push) Successful in 55s
ci / test (platform/neuroforge) (push) Successful in 56s
ci / docker-build (push) Failing after 1m10s
ci / test (services/control) (push) Successful in 1m26s
release-tag / release-image (push) Successful in 6m43s
This commit is contained in:
@@ -290,6 +290,8 @@ OLLAMA_REQUIRE_EMBEDDING_MODEL=true
|
||||
# OLLAMA_MODEL=qwen3:8b
|
||||
# Embedding-Modell für RAG.
|
||||
OLLAMA_EMBEDDING_MODEL=embeddinggemma
|
||||
# Optional: Bearer token sent as Authorization: Bearer <token> to Ollama-compatible endpoints.
|
||||
OLLAMA_API_KEY=
|
||||
|
||||
# Modellspezifisches Retrieval-Prompting.
|
||||
# auto = Modell automatisch erkennen; für embeddinggemma empfohlen.
|
||||
@@ -799,4 +801,4 @@ QUEUE_SIZE=256
|
||||
# Ticketpipelines gleichzeitig verteilen, wie Worker aktiv sind. Für drei
|
||||
# gleichartige Nodes ist WORKERS=3 ein sinnvoller Lasttest; jeder Node bleibt
|
||||
# zusätzlich durch OLLAMA_NODE_MAX_INFLIGHT begrenzt.
|
||||
WORKERS=2
|
||||
WORKERS=2
|
||||
|
||||
@@ -62,7 +62,7 @@ func main() {
|
||||
nodes = append(nodes, ollama.NodeConfig{Name: name, URL: nodeURL, Weight: weight})
|
||||
}
|
||||
o, err := ollama.NewPool(ollama.PoolConfig{
|
||||
Nodes: nodes, RoutingMode: cfg.OllamaRoutingMode, NodeMaxInflight: cfg.OllamaNodeMaxInflight,
|
||||
APIKey: cfg.OllamaAPIKey, Nodes: nodes, RoutingMode: cfg.OllamaRoutingMode, NodeMaxInflight: cfg.OllamaNodeMaxInflight,
|
||||
HealthInterval: cfg.OllamaNodeHealthInterval, FailureCooldown: cfg.OllamaNodeFailureCooldown,
|
||||
NodeRequestTimeout: cfg.OllamaNodeRequestTimeout, FailoverEnabled: cfg.OllamaFailoverEnabled,
|
||||
FailoverAttempts: cfg.OllamaFailoverAttempts, RequireSameModelDigest: cfg.OllamaRequireSameDigest,
|
||||
|
||||
@@ -8,6 +8,7 @@ services:
|
||||
DATA_DIR: /app/data
|
||||
KNOWLEDGE_DIR: /app/knowledge
|
||||
OLLAMA_URL: ${OLLAMA_URL:-http://ollama:11434}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-10m}
|
||||
OLLAMA_NUM_PREDICT: ${OLLAMA_NUM_PREDICT:-768}
|
||||
OLLAMA_JSON_RETRIES: ${OLLAMA_JSON_RETRIES:-1}
|
||||
|
||||
@@ -24,6 +24,7 @@ services:
|
||||
DATA_DIR: /app/data
|
||||
KNOWLEDGE_DIR: /app/knowledge
|
||||
OLLAMA_URL: ${OLLAMA_URL:-http://ollama:11434}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
# Local CPU inference can take several minutes on the first request.
|
||||
OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-10m}
|
||||
OLLAMA_NUM_PREDICT: ${OLLAMA_NUM_PREDICT:-768}
|
||||
|
||||
@@ -39,6 +39,7 @@ type Config struct {
|
||||
GLPIAllowedStatusIDs []int64
|
||||
|
||||
OllamaURL string // legacy single-node value
|
||||
OllamaAPIKey string // optional Bearer token sent to every Ollama node
|
||||
OllamaURLs []string
|
||||
OllamaNodeNames []string
|
||||
OllamaNodeWeights []int
|
||||
@@ -246,6 +247,7 @@ func Load() (Config, error) {
|
||||
GLPIAllowInsecureHTTP: envBool("GLPI_ALLOW_INSECURE_HTTP", false),
|
||||
GLPIAllowedStatusIDs: envInt64List("GLPI_ALLOWED_STATUS_IDS", "1"),
|
||||
OllamaURL: strings.TrimRight(env("OLLAMA_URL", "http://ollama:11434"), "/"),
|
||||
OllamaAPIKey: strings.TrimSpace(os.Getenv("OLLAMA_API_KEY")),
|
||||
OllamaURLs: envStringListPreserveCase("OLLAMA_URLS", ""),
|
||||
OllamaNodeNames: envStringListPreserveCase("OLLAMA_NODE_NAMES", ""),
|
||||
OllamaNodeWeights: envIntListAllowEmpty("OLLAMA_NODE_WEIGHTS"),
|
||||
|
||||
@@ -29,6 +29,7 @@ type NodeConfig struct {
|
||||
}
|
||||
|
||||
type PoolConfig struct {
|
||||
APIKey string
|
||||
Nodes []NodeConfig
|
||||
RoutingMode string
|
||||
NodeMaxInflight int
|
||||
@@ -390,6 +391,7 @@ func (p *Pool) checkNode(ctx context.Context, n *poolNode) (string, string, erro
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
setBearer(req, p.cfg.APIKey)
|
||||
resp, err := p.http.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
@@ -559,6 +561,7 @@ func (p *Pool) doPost(ctx context.Context, n *poolNode, path string, body []byte
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
setBearer(req, p.cfg.APIKey)
|
||||
resp, err := p.http.Do(req)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
@@ -574,6 +577,13 @@ func (p *Pool) doPost(ctx context.Context, n *poolNode, path string, body []byte
|
||||
return resp.StatusCode, raw, nil
|
||||
}
|
||||
|
||||
func setBearer(req *http.Request, token string) {
|
||||
token = strings.TrimSpace(token)
|
||||
if token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) selectNode(ctx context.Context, excluded map[string]struct{}, stage string) (*poolNode, int64, error) {
|
||||
for {
|
||||
nodes := p.orderedCandidates(excluded, stage)
|
||||
|
||||
@@ -388,3 +388,41 @@ func TestPoolFastestRecentProbesUnmeasuredNodes(t *testing.T) {
|
||||
t.Fatalf("fastest_recent must measure both nodes before preferring one: a=%d b=%d", callsA.Load(), callsB.Load())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoolSendsBearerToHealthAndInference(t *testing.T) {
|
||||
const token = "ollama-secret"
|
||||
var tagsAuth, embedAuth string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/tags":
|
||||
tagsAuth = r.Header.Get("Authorization")
|
||||
tagsResponse(w, "chat-digest", "embed-digest")
|
||||
case "/api/embed":
|
||||
embedAuth = r.Header.Get("Authorization")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"embeddings": [][]float64{{1, 0}}})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
c, err := NewPool(PoolConfig{
|
||||
APIKey: token, Nodes: []NodeConfig{{Name: "secure", URL: srv.URL, Weight: 1}}, RoutingMode: "least_inflight",
|
||||
NodeMaxInflight: 1, HealthInterval: time.Minute, FailureCooldown: time.Second, NodeRequestTimeout: time.Second,
|
||||
FailoverEnabled: false, FailoverAttempts: 1, RequireSameModelDigest: true, RequireEmbeddingModel: true, Model: "m", EmbeddingModel: "e",
|
||||
}, "m", "e", "de-DE", "formal", 128, time.Minute, false, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c.Ping(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := c.Embed(context.Background(), []string{"x"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tagsAuth != "Bearer "+token {
|
||||
t.Fatalf("tags authorization=%q", tagsAuth)
|
||||
}
|
||||
if embedAuth != "Bearer "+token {
|
||||
t.Fatalf("embed authorization=%q", embedAuth)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user