Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions internal/llm/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func bridgeLoadModel(modelPath string, nCtx, nGPULayers, nThreads int) (*cgoMode
if model == nil {
return nil, fmt.Errorf("E_MODEL_LOAD_FAILED: llama_model_load_from_file returned null for %q", modelPath)
}
fmt.Printf("DEBUG: load model=%p, vocab=%p, path=%q\n", model, C.llama_model_get_vocab(model), modelPath)

cparams := C.llama_context_default_params()
cparams.n_ctx = C.uint32_t(nCtx)
Expand Down Expand Up @@ -90,7 +91,9 @@ func bridgeLoadAdapter(cm *cgoModel, adapterPath string) error {
}

func (m *cgoModel) vocab() *C.struct_llama_vocab {
return C.llama_model_get_vocab(m.model)
v := C.llama_model_get_vocab(m.model)
fmt.Printf("DEBUG: vocab() m.model=%p, vocab=%p\n", m.model, v)
return v
}

func (m *cgoModel) nCtx() int {
Expand Down Expand Up @@ -118,15 +121,20 @@ func bridgeTokenize(vocab *C.struct_llama_vocab, text string, addSpecial bool) (
ctext := C.CString(text)
defer C.free(unsafe.Pointer(ctext))

n := C.llama_tokenize(vocab, ctext, C.int32_t(len(text)), nil, 0, C.bool(addSpecial), false)
if n <= 0 {
return nil, fmt.Errorf("tokenize failed: text too long or empty")
}

ctokens := make([]C.llama_token, n)
n = C.llama_tokenize(vocab, ctext, C.int32_t(len(text)), &ctokens[0], C.int32_t(len(ctokens)), C.bool(addSpecial), false)
textLen := C.int32_t(len(text))
// First call: estimate buffer size. Some llama.cpp versions don't support
// NULL tokens for counting, so start with a reasonable estimate.
nTokens := C.int32_t(len(text)*2 + 16)
ctokens := make([]C.llama_token, nTokens)
n := C.llama_tokenize(vocab, ctext, textLen, &ctokens[0], nTokens, C.bool(addSpecial), false)
if n < 0 {
return nil, fmt.Errorf("tokenize failed on second pass")
// Buffer too small, retry with exact size returned (negative = required size)
nTokens = C.int32_t(-n)
ctokens = make([]C.llama_token, nTokens)
n = C.llama_tokenize(vocab, ctext, textLen, &ctokens[0], nTokens, C.bool(addSpecial), false)
if n < 0 {
return nil, fmt.Errorf("tokenize failed: %d", n)
}
}

tokens := make([]int32, n)
Expand Down
13 changes: 11 additions & 2 deletions internal/server/middleware.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package server

import (
"bytes"
"encoding/json"
"io"
"net/http"
"sync"
"time"
Expand Down Expand Up @@ -48,15 +50,22 @@ func (rl *rateLimiter) allow(ip string) bool {
}

// validateJSONBody checks that the request body is valid JSON for POST requests.
// It reads the body into memory, validates JSON, then replaces the body reader
// so downstream handlers can still read it.
func validateJSONBody(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost && r.Body != nil {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, `{"error":"invalid JSON body","code":"E_INVALID_JSON"}`, http.StatusBadRequest)
return
}
var v interface{}
if err := json.NewDecoder(r.Body).Decode(&v); err != nil {
if err := json.Unmarshal(body, &v); err != nil {
http.Error(w, `{"error":"invalid JSON body","code":"E_INVALID_JSON"}`, http.StatusBadRequest)
return
}
_ = v
r.Body = io.NopCloser(bytes.NewReader(body))
}
next.ServeHTTP(w, r)
}
Expand Down
Loading