ronantakizawa/codereview-bench huggingface dataset:
Repository: ollama/ollama
File: server/routes.go (Go)

Code before:
	}

	ctx := c.Request.Context()

	embedWithRetry := func(text string) ([]float32, int, error) {
		emb, tokCount, err := r.Embedding(ctx, text, false)
		if err == nil {
			return emb, tokCount, nil
		}

		var serr api.StatusError
		if !errors.As(err, &serr) || serr.StatusCode != http.StatusBadRequest {
			return nil, 0, err
		}
		if req.Truncate != nil && !*req.Truncate {
			return nil, 0, err
		}

		tokens, err := r.Tokenize(ctx, text)
		if err != nil {
			return nil, 0, err
		}

		ctxLen := min(opts.NumCtx, int(kvData.ContextLength()))
		if bos := kvData.Uint("tokenizer.ggml.bos_token_id"); len(tokens) > 0 && tokens[0] != int(bos) && kvData.Bool("add_bos_token", true) {
			ctxLen--
		}
		if eos := kvData.Uint("tokenizer.ggml.eos_token_id"); len(tokens) > 0 && tokens[len(tokens)-1] != int(eos) && kvData.Bool("add_eos_token", true) {
			ctxLen--
		}

		if len(tokens) <= ctxLen {
			return nil, 0, err
		}
		if ctxLen <= 0 {
			return nil, 0, fmt.Errorf("input after truncation exceeds maximum context length")
		}

		truncatedTokens := tokens[:ctxLen]
		truncated, err := r.Detokenize(ctx, truncatedTokens)
		if err != nil {
			return nil, 0, err
		}
		return r.Embedding(ctx, truncated, true)
	}

	var g errgroup.Group
	embeddings := make([][]float32, len(input))
	var totalTokens uint64
	for i, text := range input {
		i := i

Review comment (suggestion):
I would add a `TODO` here to avoid reaching into the `kvData`, but we don't have to solve it in this PR

Code after:
	}

	ctx := c.Request.Context()

	embedWithRetry := func(text string) ([]float32, int, error) {
		emb, tokCount, err := r.Embedding(ctx, text)
		if err == nil {
			return emb, tokCount, nil
		}

		var serr api.StatusError
		if !errors.As(err, &serr) || serr.StatusCode != http.StatusBadRequest {
			return nil, 0, err
		}
		if req.Truncate != nil && !*req.Truncate {
			return nil, 0, err
		}

		tokens, err := r.Tokenize(ctx, text)
		if err != nil {
			return nil, 0, err
		}

		// TODO @nicolepardal: avoid reaching into kvData here; pass required tokenizer metadata via model/options instead
		ctxLen := min(opts.NumCtx, int(kvData.ContextLength()))
		if bos := kvData.Uint("tokenizer.ggml.bos_token_id"); len(tokens) > 0 && tokens[0] != int(bos) && kvData.Bool("add_bos_token", true) {
			ctxLen--
		}
		if eos := kvData.Uint("tokenizer.ggml.eos_token_id"); len(tokens) > 0 && tokens[len(tokens)-1] != int(eos) && kvData.Bool("add_eos_token", true) {
			ctxLen--
		}

		if len(tokens) <= ctxLen {
			return nil, 0, fmt.Errorf("input exceeds maximum context length and cannot be truncated further")
		}
		if ctxLen <= 0 {
			return nil, 0, fmt.Errorf("input after truncation exceeds maximum context length")
		}

		truncatedTokens := tokens[:ctxLen]
		truncated, err := r.Detokenize(ctx, truncatedTokens)
		if err != nil {
			return nil, 0, err
		}
		return r.Embedding(ctx, truncated)
	}

	var g errgroup.Group
	embeddings := make([][]float32, len(input))
	var totalTokens uint64
	for i, text := range input {
Repository: ollama/ollama
File: server/routes.go (Go)

Code before:
		}
		if eos := kvData.Uint("tokenizer.ggml.eos_token_id"); len(tokens) > 0 && tokens[len(tokens)-1] != int(eos) && kvData.Bool("add_eos_token", true) {
			ctxLen--
		}

		if len(tokens) <= ctxLen {
			return nil, 0, err
		}
		if ctxLen <= 0 {
			return nil, 0, fmt.Errorf("input after truncation exceeds maximum context length")
		}

		truncatedTokens := tokens[:ctxLen]
		truncated, err := r.Detokenize(ctx, truncatedTokens)
		if err != nil {
			return nil, 0, err
		}
		return r.Embedding(ctx, truncated, true)
	}

	var g errgroup.Group
	embeddings := make([][]float32, len(input))
	var totalTokens uint64
	for i, text := range input {
		i := i
		text := text
		g.Go(func() error {
			embedding, tokenCount, err := embedWithRetry(text)
			if err != nil {
				return err
			}
			// TODO: this first normalization should be done by the model
			embedding = normalize(embedding)
			if req.Dimensions > 0 && req.Dimensions < len(embedding) {
				embedding = normalize(embedding[:req.Dimensions])
			}
			embeddings[i] = embedding
			atomic.AddUint64(&totalTokens, uint64(tokenCount))
			return nil
		})
	}

	if err := g.Wait(); err != nil {
		var serr api.StatusError
		if errors.As(err, &serr) {
			c.AbortWithStatusJSON(serr.StatusCode, gin.H{
				"error": strings.TrimSpace(serr.ErrorMessage),
			})
			return
		}


Review comment (suggestion):
I would double check we need these (we might, but as of a new version of Go I think `i` and `text` are no longer shared).

Code after:
			ctxLen--
		}
		if eos := kvData.Uint("tokenizer.ggml.eos_token_id"); len(tokens) > 0 && tokens[len(tokens)-1] != int(eos) && kvData.Bool("add_eos_token", true) {
			ctxLen--
		}

		if len(tokens) <= ctxLen {
			return nil, 0, fmt.Errorf("input exceeds maximum context length and cannot be truncated further")
		}
		if ctxLen <= 0 {
			return nil, 0, fmt.Errorf("input after truncation exceeds maximum context length")
		}

		truncatedTokens := tokens[:ctxLen]
		truncated, err := r.Detokenize(ctx, truncatedTokens)
		if err != nil {
			return nil, 0, err
		}
		return r.Embedding(ctx, truncated)
	}

	var g errgroup.Group
	embeddings := make([][]float32, len(input))
	var totalTokens uint64
	for i, text := range input {
		g.Go(func() error {
			embedding, tokenCount, err := embedWithRetry(text)
			if err != nil {
				return err
			}
			// TODO: this first normalization should be done by the model
			embedding = normalize(embedding)
			if req.Dimensions > 0 && req.Dimensions < len(embedding) {
				embedding = normalize(embedding[:req.Dimensions])
			}
			embeddings[i] = embedding
			atomic.AddUint64(&totalTokens, uint64(tokenCount))
			return nil
		})
	}

	if err := g.Wait(); err != nil {
		var serr api.StatusError
		if errors.As(err, &serr) {
			c.AbortWithStatusJSON(serr.StatusCode, gin.H{
				"error": strings.TrimSpace(serr.ErrorMessage),
			})
			return
		}

		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
Repository: ollama/ollama
File: runner/ollamarunner/runner.go (Go)

Code before:
	if err != nil {
		return nil, fmt.Errorf("failed to process inputs: %w", err)
	} else if len(inputs) == 0 {
		return nil, errors.New("no input provided")
	}

	if params.numKeep < 0 {
		params.numKeep = int32(len(inputs))
	}

	// Ensure that at least 1 input can be discarded during shift
	params.numKeep = min(params.numKeep, s.cache.numCtx-1)

	// Basic runner-side guard: if truncation is enabled and num_ctx <= 1, return an error
	if params.truncate && s.cache.numCtx <= 1 {
		return nil, fmt.Errorf("input after truncation exceeds maximum context length")
	}

	if int32(len(inputs)) > s.cache.numCtx {
		if !params.truncate {
			return nil, errorInputTooLong
		}

		// Embeddings and generation both return the same too-long error when truncate is enabled

		discard := int32(len(inputs)) - s.cache.numCtx
		promptStart := params.numKeep + discard

		sameBatch := 0
		for i, inp := range inputs {
			if sameBatch > 0 {
				sameBatch--

				if promptStart == int32(i) {
					promptStart++
				}
			} else if promptStart == int32(i) {
				break
			}

			if inp.SameBatch != 0 {
				if int32(i) < params.numKeep {
					return nil, fmt.Errorf("SameBatch may not be specified within numKeep (index: %v numKeep: %v SameBatch: %v)", i, params.numKeep, inp.SameBatch)
				}

				sameBatch = inp.SameBatch
			}
		}

		if promptStart >= int32(len(inputs)) {
			return nil, errors.New("entire prompt removed by truncation")

Review comment (performance):
I don't think we have to move this line from below the `if int32(len(inputs)) > s.cache.numCtx`. Furthermore the comment here isn't super relevant to the code and can be removed

Code after:
	if err != nil {
		return nil, fmt.Errorf("failed to process inputs: %w", err)
	} else if len(inputs) == 0 {
		return nil, errors.New("no input provided")
	}

	if params.numKeep < 0 {
		params.numKeep = int32(len(inputs))
	}

	// Ensure that at least 1 input can be discarded during shift
	params.numKeep = min(params.numKeep, s.cache.numCtx-1)

	if int32(len(inputs)) > s.cache.numCtx {
		if !params.truncate {
			return nil, errorInputTooLong
		}

		discard := int32(len(inputs)) - s.cache.numCtx

		promptStart := params.numKeep + discard

		// If we need to truncate in the middle of a unbreakable batch, remove the entire batch
		sameBatch := 0
		for i, inp := range inputs {
			if sameBatch > 0 {
				sameBatch--

				if promptStart == int32(i) {
					promptStart++
				}
			} else if promptStart == int32(i) {
				break
			}

			if inp.SameBatch != 0 {
				if int32(i) < params.numKeep {
					return nil, fmt.Errorf("SameBatch may not be specified within numKeep (index: %v numKeep: %v SameBatch: %v)", i, params.numKeep, inp.SameBatch)
				}

				sameBatch = inp.SameBatch
			}
		}

		if promptStart >= int32(len(inputs)) {
			return nil, errors.New("entire prompt removed by truncation")
		}

		newInputs := inputs[:params.numKeep]
		newInputs = append(newInputs, inputs[promptStart:]...)
Repository: ollama/ollama
File: runner/ollamarunner/runner.go (Go)

Code before:
	sampler    sample.Sampler
	embedding  bool
	shift      bool
	truncate   bool
}

var errorInputTooLong = errors.New("the input length exceeds the context length")

func (s *Server) NewSequence(prompt string, images []llm.ImageData, params NewSequenceParams) (*Sequence, error) {
	s.ready.Wait()

	inputs, ctxs, mmStore, err := s.inputs(prompt, images)
	if err != nil {
		return nil, fmt.Errorf("failed to process inputs: %w", err)
	} else if len(inputs) == 0 {
		return nil, errors.New("no input provided")
	}

	if params.numKeep < 0 {
		params.numKeep = int32(len(inputs))
	}

	// Ensure that at least 1 input can be discarded during shift
	params.numKeep = min(params.numKeep, s.cache.numCtx-1)

	discard := int32(len(inputs)) - s.cache.numCtx

	if int32(len(inputs)) > s.cache.numCtx {
		if !params.truncate {
			return nil, errorInputTooLong
		}

		promptStart := params.numKeep + discard

		// If we need to truncate in the middle of a unbreakable batch, remove the entire batch
		sameBatch := 0
		for i, inp := range inputs {
			if sameBatch > 0 {
				sameBatch--

				if promptStart == int32(i) {
					promptStart++
				}
			} else if promptStart == int32(i) {
				break
			}

			if inp.SameBatch != 0 {
				if int32(i) < params.numKeep {
					return nil, fmt.Errorf("SameBatch may not be specified within numKeep (index: %v numKeep: %v SameBatch: %v)", i, params.numKeep, inp.SameBatch)
				}

Review comment (suggestion):
`discard` should be inside of the if statement - you should be able to just leave this logic as it is currently.

Code after:
	sampler    sample.Sampler
	embedding  bool
	shift      bool
	truncate   bool
}

var errorInputTooLong = errors.New("the input length exceeds the context length")

func (s *Server) NewSequence(prompt string, images []llm.ImageData, params NewSequenceParams) (*Sequence, error) {
	s.ready.Wait()

	inputs, ctxs, mmStore, err := s.inputs(prompt, images)
	if err != nil {
		return nil, fmt.Errorf("failed to process inputs: %w", err)
	} else if len(inputs) == 0 {
		return nil, errors.New("no input provided")
	}

	if params.numKeep < 0 {
		params.numKeep = int32(len(inputs))
	}

	// Ensure that at least 1 input can be discarded during shift
	params.numKeep = min(params.numKeep, s.cache.numCtx-1)

	if int32(len(inputs)) > s.cache.numCtx {
		if !params.truncate {
			return nil, errorInputTooLong
		}

		discard := int32(len(inputs)) - s.cache.numCtx

		promptStart := params.numKeep + discard

		// If we need to truncate in the middle of a unbreakable batch, remove the entire batch
		sameBatch := 0
		for i, inp := range inputs {
			if sameBatch > 0 {
				sameBatch--

				if promptStart == int32(i) {
					promptStart++
				}
			} else if promptStart == int32(i) {
				break
			}

			if inp.SameBatch != 0 {
				if int32(i) < params.numKeep {
					return nil, fmt.Errorf("SameBatch may not be specified within numKeep (index: %v numKeep: %v SameBatch: %v)", i, params.numKeep, inp.SameBatch)
				}
Repository: ollama/ollama
File: cmd/cmd.go (Go)

Code before:
}

type progressWriter struct {
	n atomic.Int64
}

func (w *progressWriter) Write(p []byte) (n int, err error) {
	w.n.Add(int64(len(p)))
	return len(p), nil
}

func loadOrUnloadModel(cmd *cobra.Command, opts *runOptions) error {
	p := progress.NewProgress(os.Stderr)
	defer p.StopAndClear()

	spinner := progress.NewSpinner("")
	p.Add("", spinner)

	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}

	if info, err := client.Show(cmd.Context(), &api.ShowRequest{Model: opts.Model}); err != nil {
		return err
	} else if info.RemoteHost != "" {
		// Cloud model, no need to load/unload
		return nil
	}

	req := &api.GenerateRequest{
		Model:     opts.Model,
		KeepAlive: opts.KeepAlive,

		// pass Think here so we fail before getting to the chat prompt if the model doesn't support it
		Think: opts.Think,
	}

	return client.Generate(cmd.Context(), req, func(r api.GenerateResponse) error {
		if r.RemoteModel != "" && opts.ShowConnect {
			p.StopAndClear()
			if strings.HasPrefix(r.RemoteHost, "https://ollama.com") {
				fmt.Fprintf(os.Stderr, "Connecting to '%s' on 'ollama.com' ⚡\n", r.RemoteModel)
			} else {
				fmt.Fprintf(os.Stderr, "Connecting to '%s' on '%s'\n", r.RemoteModel, r.RemoteHost)
			}
		}
		return nil
	})
}


Review comment (suggestion):
this should render the rest of the change redundant

```suggestion
	} else if info.RemoteHost != "" && !opts.ShowConnect {
```

Code after:
}

type progressWriter struct {
	n atomic.Int64
}

func (w *progressWriter) Write(p []byte) (n int, err error) {
	w.n.Add(int64(len(p)))
	return len(p), nil
}

func loadOrUnloadModel(cmd *cobra.Command, opts *runOptions) error {
	p := progress.NewProgress(os.Stderr)
	defer p.StopAndClear()

	spinner := progress.NewSpinner("")
	p.Add("", spinner)

	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}

	if info, err := client.Show(cmd.Context(), &api.ShowRequest{Model: opts.Model}); err != nil {
		return err
	} else if info.RemoteHost != "" {
		// Cloud model, no need to load/unload
		if opts.ShowConnect {
			p.StopAndClear()
			if strings.HasPrefix(info.RemoteHost, "https://ollama.com") {
				fmt.Fprintf(os.Stderr, "Connecting to '%s' on 'ollama.com' ⚡\n", info.RemoteModel)
			} else {
				fmt.Fprintf(os.Stderr, "Connecting to '%s' on '%s'\n", info.RemoteModel, info.RemoteHost)
			}
		}
		return nil
	}

	req := &api.GenerateRequest{
		Model:     opts.Model,
		KeepAlive: opts.KeepAlive,

		// pass Think here so we fail before getting to the chat prompt if the model doesn't support it
		Think: opts.Think,
	}

	return client.Generate(cmd.Context(), req, func(r api.GenerateResponse) error {
		return nil
	})
}
Repository: ollama/ollama
File: runner/common/logprob.go (Go)

Code before:
package common

import (
	"math"
	"sort"

	"github.com/ollama/ollama/llm"
)

func stringToByteInts(s string) []int {
	if s == "" {
		return nil
	}

	raw := []byte(s)
	ints := make([]int, len(raw))
	for i, b := range raw {
		ints[i] = int(b)
	}
	return ints
}

// TokenDecoderFunc is a function that converts token IDs to text.
type TokenDecoderFunc func(tokenID int) string

// CalculateLogprobs converts raw logits to log probabilities and finds top K tokens.
// It uses numerically stable softmax to compute log probabilities.
func CalculateLogprobs(logits []float32, selectedToken int, topK int, decoder TokenDecoderFunc) []llm.Logprob {
	if len(logits) == 0 {
		return nil
	}

	// Step 1: Convert logits to log probabilities using numerically stable softmax
	maxLogit := logits[0]
	for _, logit := range logits[1:] {

Review comment (suggestion):
I _think_ you might be able to use `[]byte(s)` instead of this function. However when returning it in the API you'd need to make sure it converts to the integer values instead of a byte slice – I think you could create at type with a custom marshal function instead

Code after:
package common

import (
	"math"
	"sort"

	"github.com/ollama/ollama/llm"
)

// TokenDecoderFunc is a function that converts token IDs to text.
type TokenDecoderFunc func(tokenID int) string

// CalculateLogprobs converts raw logits to log probabilities and finds top K tokens.
// It uses numerically stable softmax to compute log probabilities.
func CalculateLogprobs(logits []float32, selectedToken int, topK int, decoder TokenDecoderFunc) []llm.Logprob {
	if len(logits) == 0 {
		return nil
	}

	// Step 1: Convert logits to log probabilities using numerically stable softmax
	maxLogit := logits[0]
	for _, logit := range logits[1:] {
		if logit > maxLogit {
			maxLogit = logit
		}
	}

	var sumExp float64
	for _, logit := range logits {
		sumExp += math.Exp(float64(logit - maxLogit))
	}
	logSumExp := float32(math.Log(sumExp))

	logProbs := make([]float32, len(logits))
	for i, logit := range logits {
Repository: ollama/ollama
File: model/models/nomicbert/model.go (Go)

Code before:

	AttentionNorm *nn.LayerNorm `gguf:"attn_output_norm"`

	*MLP

	MLPNorm *nn.LayerNorm `gguf:"layer_output_norm"`
}

type Attention struct {
	QKV    *nn.Linear `gguf:"attn_qkv"`
	Output *nn.Linear `gguf:"attn_output"`
}

type MLP struct {
	Gate *nn.Linear `gguf:"ffn_gate"`
	Up   *nn.Linear `gguf:"ffn_up"`
	Down *nn.Linear `gguf:"ffn_down"`
}

// Forward implements model.Model interface
func (m *Model) Forward(ctx ml.Context, batch input.Batch) (ml.Tensor, error) {
	seqLen := batch.Inputs.Dim(0)

	hiddenStates := m.TokenEmbedding.Forward(ctx, batch.Inputs)

	typeEmbed := m.TypeEmbedding.Weight.View(ctx, 0, m.hiddenSize)
	hiddenStates = hiddenStates.Add(ctx, typeEmbed)

	// no position embedding - we use ROPE instead

	hiddenStates = m.TokenEmbeddingNorm.Forward(ctx, hiddenStates, m.eps)

	positions := ctx.Arange(0, float32(seqLen), 1, ml.DTypeI32)

	for _, layer := range m.Layers {
		hiddenStates = layer.Forward(ctx, hiddenStates, positions, &m.Options)
	}

	hiddenStates = m.poolingType.Forward(ctx, hiddenStates)

	if m.normalize {
		hiddenStates = hiddenStates.L2Norm(ctx, 1e-12)
	}

	return hiddenStates, nil
}

func (e *EncoderLayer) Forward(ctx ml.Context, hiddenStates ml.Tensor, positions ml.Tensor, opts *Options) ml.Tensor {
	residual := hiddenStates
	hiddenStates = e.Attention.Forward(ctx, hiddenStates, positions, opts)
	hiddenStates = hiddenStates.Add(ctx, residual)

Review comment (suggestion):
If you rebase, you'll be able to use the slice operation


```suggestion
	typeEmbed := m.TypeEmbedding.Weight.Slice(ctx, 1, 0, 1, 1)
```

Code after:
	*Attention

	AttentionNorm *nn.LayerNorm `gguf:"attn_output_norm"`

	*MLP

	MLPNorm *nn.LayerNorm `gguf:"layer_output_norm"`
}

type Attention struct {
	QKV    *nn.Linear `gguf:"attn_qkv"`
	Output *nn.Linear `gguf:"attn_output"`
}

type MLP struct {
	Gate *nn.Linear `gguf:"ffn_gate"`
	Up   *nn.Linear `gguf:"ffn_up"`
	Down *nn.Linear `gguf:"ffn_down"`
}

func (m *Model) Forward(ctx ml.Context, batch input.Batch) (ml.Tensor, error) {
	hiddenStates := m.TokenEmbedding.Forward(ctx, batch.Inputs)

	typeEmbed := m.TypeEmbedding.Weight.Slice(ctx, 1, 0, 1, 1)
	hiddenStates = hiddenStates.Add(ctx, typeEmbed)

	hiddenStates = m.TokenEmbeddingNorm.Forward(ctx, hiddenStates, m.eps)

	positions := ctx.Input().FromInts(batch.Positions, len(batch.Positions))

	for _, layer := range m.Layers {
		hiddenStates = layer.Forward(ctx, hiddenStates, positions, &m.Options)
	}

	hiddenStates = m.poolingType.Forward(ctx, hiddenStates)

	if m.normalize {
		hiddenStates = hiddenStates.L2Norm(ctx, 1e-12)
	}

	return hiddenStates, nil
}

func (e *EncoderLayer) Forward(ctx ml.Context, hiddenStates ml.Tensor, positions ml.Tensor, opts *Options) ml.Tensor {
	residual := hiddenStates
	hiddenStates = e.Attention.Forward(ctx, hiddenStates, positions, opts)
	hiddenStates = hiddenStates.Add(ctx, residual)
	hiddenStates = e.AttentionNorm.Forward(ctx, hiddenStates, opts.eps)

	residual = hiddenStates
	hiddenStates = e.MLP.Forward(ctx, hiddenStates)
Repository: ollama/ollama
File: app/ui/ui.go (Go)

Code before:
	WebSearch    bool   // if true, the server will use single-turn browser tool to fulfill the user's request
	Agent        bool   // if true, the server will use multi-turn tools to fulfill the user's request
	WorkingDir   string // Working directory for all agent operations

	// Dev is true if the server is running in development mode
	Dev bool
}

func (s *Server) log() *slog.Logger {
	if s.Logger == nil {
		return slog.Default()
	}
	return s.Logger
}

// ollamaProxy creates a reverse proxy handler to the Ollama server
func (s *Server) ollamaProxy() http.Handler {
	var (
		proxy     http.Handler
		proxyOnce sync.Once
		proxyErr  error
	)

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		proxyOnce.Do(func() {
			if err := waitForServer(r.Context()); err != nil {
				proxyErr = err
				return
			}

			target := envconfig.Host()
			s.log().Info("configuring ollama proxy", "target", target.String())

			p := httputil.NewSingleHostReverseProxy(target)

			originalDirector := p.Director
			p.Director = func(req *http.Request) {
				originalDirector(req)
				req.Host = target.Host
				s.log().Debug("proxying request", "method", req.Method, "path", req.URL.Path, "target", target.Host)
			}

			p.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
				s.log().Error("proxy error", "error", err, "path", r.URL.Path, "target", target.String())
				http.Error(w, "proxy error: "+err.Error(), http.StatusBadGateway)
			}

			proxy = p
		})

		if proxyErr != nil {

Review comment (suggestion):
We should just use a `context.Background` here rather than the request context so that it continues to wait the the proxy server even when the client cancels

Code after:
	Agent        bool   // if true, the server will use multi-turn tools to fulfill the user's request
	WorkingDir   string // Working directory for all agent operations

	// Dev is true if the server is running in development mode
	Dev bool
}

func (s *Server) log() *slog.Logger {
	if s.Logger == nil {
		return slog.Default()
	}
	return s.Logger
}

// ollamaProxy creates a reverse proxy handler to the Ollama server
func (s *Server) ollamaProxy() http.Handler {
	var (
		proxy   http.Handler
		proxyMu sync.Mutex
	)

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		proxyMu.Lock()
		p := proxy
		proxyMu.Unlock()

		if p == nil {
			proxyMu.Lock()
			if proxy == nil {
				var err error
				for i := range 2 {
					if i > 0 {
						s.log().Warn("ollama server not ready, retrying", "attempt", i+1)
						time.Sleep(1 * time.Second)
					}

					err = WaitForServer(context.Background(), 10*time.Second)
					if err == nil {
						break
					}
				}

				if err != nil {
					proxyMu.Unlock()
					s.log().Error("ollama server not ready after retries", "error", err)
					http.Error(w, "Ollama server is not ready", http.StatusServiceUnavailable)
					return
				}

				target := envconfig.Host()
				s.log().Info("configuring ollama proxy", "target", target.String())