Repository: ollama/ollama
File: server/routes.go (Go)

Code before:
}

// contextLimitPrompt builds a prompt to send to a running model while trimming it to fit the template into the max context length
func contextLimitPrompt(ctx context.Context, prompts []string, ctxLen int) (string, error) {
	if len(prompts) == 0 {
		return "", nil
	}

	prompt := ""
	promptLen := 0

	// always process the last prompt
	lastPrompt := prompts[len(prompts)-1]
	enc, err := loaded.runner.Encode(ctx, lastPrompt)
	if err != nil {
		return "", err
	}
	prompt = lastPrompt
	promptLen = len(enc)

	if len(prompts) == 1 {
		return prompt, nil
	}

	// iterate over the rest of the prompts in reverse order until we reach the max context length
	for i := len(prompts) - 2; i >= 0; i-- {
		enc, err := loaded.runner.Encode(ctx, prompts[i])
		if err != nil {
			return "", err
		}
		if promptLen+len(enc) > loaded.NumCtx {
			// the context window is full, stop adding to the chat history
			break
		}
		prompt = prompts[i] + prompt
		promptLen += len(enc)
	}
	return prompt, nil
}

func ChatHandler(c *gin.Context) {
	loaded.mu.Lock()
	defer loaded.mu.Unlock()

	checkpointStart := time.Now()

	var req api.ChatRequest
	err := c.ShouldBindJSON(&req)
	switch {
	case errors.Is(err, io.EOF):
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"})

Review comment (suggestion):
I'm not sure the separation is warranted. Why not reverse the slice and iterate as normal for all messages? The special case for the first (last) message could be an additional check:

```go
var prompt string
for i, message := range slices.Reverse(prompts) {
  enc , err := loaded.runner.Encode(ctx, message)
  // check err

  if i != 0 && promptLen + len(enc) > loaded.NumCtx {
    break
  }

  prompt = message + prompt
  promptLen += len(enc)
}
```

Code after:
}

func ChatHandler(c *gin.Context) {
	loaded.mu.Lock()
	defer loaded.mu.Unlock()

	checkpointStart := time.Now()

	var req api.ChatRequest
	err := c.ShouldBindJSON(&req)
	switch {
	case errors.Is(err, io.EOF):
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"})
		return
	case err != nil:
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// validate the request
	switch {
	case req.Model == "":
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "model is required"})
		return
	case len(req.Format) > 0 && req.Format != "json":
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "format must be json"})
		return
	}

	model, err := GetModel(req.Model)
	if err != nil {
		var pErr *fs.PathError
		if errors.As(err, &pErr) {
			c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("model '%s' not found, try pulling it first", req.Model)})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	opts, err := modelOptions(model, req.Options)
	if err != nil {
		if errors.Is(err, api.ErrInvalidOpts) {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	sessionDuration := defaultSessionDuration
	if err := load(c, model, opts, sessionDuration); err != nil {