micro/go-micro · error

stream API error (%s): %s

Error message

stream API error (%s): %s

What it means

The Ollama Cloud OpenAI-compatible endpoint returned a non-200 HTTP status for a streaming chat request. The error embeds the HTTP status string (e.g. '401 Unauthorized') and the raw response body, which usually holds the server's JSON error explaining why.

Source

Thrown at ai/ollama/ollama.go:354

	apiURL := strings.TrimRight(p.opts.BaseURL, "/") + p.streamPath()
	httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, bytes.NewReader(reqBody))
	if err != nil {
		return nil, fmt.Errorf("failed to create stream request: %w", err)
	}
	httpReq.Header.Set("Content-Type", "application/json")
	httpReq.Header.Set("Accept", "text/event-stream")
	if p.opts.APIKey != "" {
		httpReq.Header.Set("Authorization", "Bearer "+p.opts.APIKey)
	}

	httpResp, err := http.DefaultClient.Do(httpReq)
	if err != nil {
		return nil, fmt.Errorf("stream API request failed: %w", err)
	}
	if httpResp.StatusCode != http.StatusOK {
		defer httpResp.Body.Close()
		respBody, _ := io.ReadAll(httpResp.Body)
		return nil, fmt.Errorf("stream API error (%s): %s", httpResp.Status, string(respBody))
	}

	return &sseStream{body: httpResp.Body, scanner: bufio.NewScanner(httpResp.Body)}, nil
}

// buildOpenAIMessages converts an ai.Request into the OpenAI chat message format.
func buildOpenAIMessages(req *ai.Request) []map[string]any {
	messages := []map[string]any{}
	if req.SystemPrompt != "" {
		messages = append(messages, map[string]any{"role": "system", "content": req.SystemPrompt})
	}
	for _, m := range req.Messages {
		messages = append(messages, map[string]any{"role": m.Role, "content": m.Content})
	}
	if req.Prompt != "" {
		messages = append(messages, map[string]any{"role": "user", "content": req.Prompt})
	}
	return messages

View on GitHub (pinned to 24529f1404)

Solutions

  1. Read the body in the error message — it contains the server's reason.
  2. 401/403: set a valid API key via WithAPIKey or the OLLAMA_API_KEY env var.
  3. 404: fix BaseURL so it ends at the host root or /v1, not a duplicated path.
  4. 400: verify the model name exists on the cloud endpoint.
  5. 429/5xx: back off and retry; the server is rate-limiting or unhealthy.

Example fix

// before
provider := ai.NewProvider("ollama", ai.WithBaseURL("https://ollama.com/v1")) // no API key -> 401
// after
provider := ai.NewProvider("ollama", ai.WithBaseURL("https://ollama.com/v1"), ai.WithAPIKey(os.Getenv("OLLAMA_API_KEY")))
Defensive patterns

Strategy: try-catch

Validate before calling

if os.Getenv("OLLAMA_API_KEY") == "" {
    return errors.New("OLLAMA_API_KEY is required for ollama.com cloud endpoints")
}

Try / catch

stream, err := provider.Stream(ctx, req)
if err != nil {
    var apiErr *ApiError // or match on status substring
    msg := err.Error()
    switch {
    case strings.Contains(msg, "401"), strings.Contains(msg, "403"):
        return errors.New("authentication failed: check OLLAMA_API_KEY")
    case strings.Contains(msg, "429"):
        // back off and retry
    default:
        return err
    }
}

Prevention

When it happens

Trigger: Stream() in cloud mode receives any status != 200: 401/403 (bad/missing API key), 404 (wrong path/BaseURL), 400 (malformed model or request), 429/5xx from ollama.com.

Common situations: Missing or expired OLLAMA_API_KEY, using an API key without cloud access, BaseURL pointing to /v1 incorrectly, requesting a model name that doesn't exist on ollama.com, rate limiting.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/d2bd9f090a4df1b5. Report an issue: GitHub.