From 86aa3d85b0d4aa4cb9e4f24889869bd23edf1bf6 Mon Sep 17 00:00:00 2001
From: Richard Palethorpe
Date: Tue, 16 Dec 2025 16:26:03 +0000
Subject: [PATCH 1/2] feat(api): Allow tracing of requests and responses
Signed-off-by: Richard Palethorpe
---
core/cli/run.go | 5 ++
core/config/application_config.go | 5 ++
core/http/middleware/trace.go | 143 ++++++++++++++++++++++++++++++
core/http/routes/openai.go | 14 ++-
core/http/routes/ui_api.go | 21 +++++
5 files changed, 186 insertions(+), 2 deletions(-)
create mode 100644 core/http/middleware/trace.go
diff --git a/core/cli/run.go b/core/cli/run.go
index 38541bd0f539..f9b530a201bb 100644
--- a/core/cli/run.go
+++ b/core/cli/run.go
@@ -80,6 +80,7 @@ type RunCMD struct {
DisableGalleryEndpoint bool `env:"LOCALAI_DISABLE_GALLERY_ENDPOINT,DISABLE_GALLERY_ENDPOINT" help:"Disable the gallery endpoints" group:"api"`
MachineTag string `env:"LOCALAI_MACHINE_TAG,MACHINE_TAG" help:"Add Machine-Tag header to each response which is useful to track the machine in the P2P network" group:"api"`
LoadToMemory []string `env:"LOCALAI_LOAD_TO_MEMORY,LOAD_TO_MEMORY" help:"A list of models to load into memory at startup" group:"models"`
+ EnableTracing bool `env:"LOCALAI_ENABLE_TRACING,ENABLE_TRACING" help:"Enable API tracing" group:"api"`
AgentJobRetentionDays int `env:"LOCALAI_AGENT_JOB_RETENTION_DAYS,AGENT_JOB_RETENTION_DAYS" default:"30" help:"Number of days to keep agent job history (default: 30)" group:"api"`
Version bool
@@ -152,6 +153,10 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error {
opts = append(opts, config.DisableRuntimeSettings)
}
+ if r.EnableTracing {
+ opts = append(opts, config.EnableTracing)
+ }
+
token := ""
if r.Peer2Peer || r.Peer2PeerToken != "" {
xlog.Info("P2P mode enabled")
diff --git a/core/config/application_config.go b/core/config/application_config.go
index fdd8810e3494..d9249beeef45 100644
--- a/core/config/application_config.go
+++ b/core/config/application_config.go
@@ -19,6 +19,7 @@ type ApplicationConfig struct {
UploadLimitMB, Threads, ContextSize int
F16 bool
Debug bool
+ EnableTracing bool
GeneratedContentDir string
UploadDir string
@@ -165,6 +166,10 @@ var EnableWatchDog = func(o *ApplicationConfig) {
o.WatchDog = true
}
+var EnableTracing = func(o *ApplicationConfig) {
+ o.EnableTracing = true
+}
+
var EnableWatchDogIdleCheck = func(o *ApplicationConfig) {
o.WatchDog = true
o.WatchDogIdle = true
diff --git a/core/http/middleware/trace.go b/core/http/middleware/trace.go
new file mode 100644
index 000000000000..d5ecd282aa86
--- /dev/null
+++ b/core/http/middleware/trace.go
@@ -0,0 +1,143 @@
+package middleware
+
+import (
+ "bytes"
+ "io"
+ "net/http"
+ "sync"
+
+ "github.com/labstack/echo/v4"
+ "github.com/mudler/LocalAI/core/application"
+ "github.com/rs/zerolog/log"
+)
+
+type APIExchange struct {
+ Request struct {
+ Method string
+ Path string
+ Headers http.Header
+ Body []byte
+ }
+ Response struct {
+ Status int
+ Headers http.Header
+ Body []byte
+ }
+}
+
+var apiLogs []APIExchange
+var mu sync.Mutex
+var logChan = make(chan APIExchange, 100) // Buffered channel for serialization
+
+func init() {
+ go func() {
+ for exchange := range logChan {
+ mu.Lock()
+ apiLogs = append(apiLogs, exchange)
+ mu.Unlock()
+ log.Debug().Msgf("Logged exchange: %s %s - Status: %d", exchange.Request.Method, exchange.Request.Path, exchange.Response.Status)
+ }
+ }()
+}
+
+type bodyWriter struct {
+ http.ResponseWriter
+ body *bytes.Buffer
+}
+
+func (w *bodyWriter) Write(b []byte) (int, error) {
+ w.body.Write(b)
+ return w.ResponseWriter.Write(b)
+}
+
+func (w *bodyWriter) Flush() {
+ if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
+ flusher.Flush()
+ }
+}
+
+// TraceMiddleware intercepts and logs JSON API requests and responses
+func TraceMiddleware(app *application.Application) echo.MiddlewareFunc {
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if !app.ApplicationConfig().EnableTracing {
+ return next(c)
+ }
+
+ // Only log if Content-Type is application/json
+ if c.Request().Header.Get("Content-Type") != "application/json" {
+ return next(c)
+ }
+
+ body, err := io.ReadAll(c.Request().Body)
+ if err != nil {
+ log.Error().Err(err).Msg("Failed to read request body")
+ return err
+ }
+
+ // Restore the body for downstream handlers
+ c.Request().Body = io.NopCloser(bytes.NewBuffer(body))
+
+ // Wrap response writer to capture body
+ resBody := new(bytes.Buffer)
+ mw := &bodyWriter{
+ ResponseWriter: c.Response().Writer,
+ body: resBody,
+ }
+ c.Response().Writer = mw
+
+ err = next(c)
+ if err != nil {
+ c.Response().Writer = mw.ResponseWriter // Restore original writer if error
+ return err
+ }
+
+ // Create exchange log
+ exchange := APIExchange{
+ Request: struct {
+ Method string
+ Path string
+ Headers http.Header
+ Body []byte
+ }{
+ Method: c.Request().Method,
+ Path: c.Path(),
+ Headers: c.Request().Header.Clone(),
+ Body: body,
+ },
+ Response: struct {
+ Status int
+ Headers http.Header
+ Body []byte
+ }{
+ Status: c.Response().Status,
+ Headers: c.Response().Header().Clone(),
+ Body: resBody.Bytes(),
+ },
+ }
+
+ // Send to channel (non-blocking)
+ select {
+ case logChan <- exchange:
+ default:
+ log.Warn().Msg("API log channel full, dropping log")
+ }
+
+ return nil
+ }
+ }
+}
+
+// GetAPILogs returns a copy of the logged API exchanges for display
+func GetAPILogs() []APIExchange {
+ mu.Lock()
+ defer mu.Unlock()
+ return append([]APIExchange{}, apiLogs...)
+}
+
+// ClearAPILogs clears the in-memory logs
+func ClearAPILogs() {
+ mu.Lock()
+ apiLogs = nil
+ mu.Unlock()
+}
diff --git a/core/http/routes/openai.go b/core/http/routes/openai.go
index 921582a6b54b..93fed71dbb59 100644
--- a/core/http/routes/openai.go
+++ b/core/http/routes/openai.go
@@ -14,16 +14,18 @@ func RegisterOpenAIRoutes(app *echo.Echo,
re *middleware.RequestExtractor,
application *application.Application) {
// openAI compatible API endpoint
+ traceMiddleware := middleware.TraceMiddleware(application)
// realtime
// TODO: Modify/disable the API key middleware for this endpoint to allow ephemeral keys created by sessions
app.GET("/v1/realtime", openai.Realtime(application))
- app.POST("/v1/realtime/sessions", openai.RealtimeTranscriptionSession(application))
- app.POST("/v1/realtime/transcription_session", openai.RealtimeTranscriptionSession(application))
+ app.POST("/v1/realtime/sessions", openai.RealtimeTranscriptionSession(application), traceMiddleware)
+ app.POST("/v1/realtime/transcription_session", openai.RealtimeTranscriptionSession(application), traceMiddleware)
// chat
chatHandler := openai.ChatEndpoint(application.ModelConfigLoader(), application.ModelLoader(), application.TemplatesEvaluator(), application.ApplicationConfig())
chatMiddleware := []echo.MiddlewareFunc{
+ traceMiddleware,
re.BuildFilteredFirstAvailableDefaultModel(config.BuildUsecaseFilterFn(config.FLAG_CHAT)),
re.SetModelAndConfig(func() schema.LocalAIRequest { return new(schema.OpenAIRequest) }),
func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -41,6 +43,7 @@ func RegisterOpenAIRoutes(app *echo.Echo,
// edit
editHandler := openai.EditEndpoint(application.ModelConfigLoader(), application.ModelLoader(), application.TemplatesEvaluator(), application.ApplicationConfig())
editMiddleware := []echo.MiddlewareFunc{
+ traceMiddleware,
re.BuildFilteredFirstAvailableDefaultModel(config.BuildUsecaseFilterFn(config.FLAG_EDIT)),
re.BuildConstantDefaultModelNameMiddleware("gpt-4o"),
re.SetModelAndConfig(func() schema.LocalAIRequest { return new(schema.OpenAIRequest) }),
@@ -59,6 +62,7 @@ func RegisterOpenAIRoutes(app *echo.Echo,
// completion
completionHandler := openai.CompletionEndpoint(application.ModelConfigLoader(), application.ModelLoader(), application.TemplatesEvaluator(), application.ApplicationConfig())
completionMiddleware := []echo.MiddlewareFunc{
+ traceMiddleware,
re.BuildFilteredFirstAvailableDefaultModel(config.BuildUsecaseFilterFn(config.FLAG_COMPLETION)),
re.BuildConstantDefaultModelNameMiddleware("gpt-4o"),
re.SetModelAndConfig(func() schema.LocalAIRequest { return new(schema.OpenAIRequest) }),
@@ -78,6 +82,7 @@ func RegisterOpenAIRoutes(app *echo.Echo,
// MCPcompletion
mcpCompletionHandler := openai.MCPCompletionEndpoint(application.ModelConfigLoader(), application.ModelLoader(), application.TemplatesEvaluator(), application.ApplicationConfig())
mcpCompletionMiddleware := []echo.MiddlewareFunc{
+ traceMiddleware,
re.BuildFilteredFirstAvailableDefaultModel(config.BuildUsecaseFilterFn(config.FLAG_CHAT)),
re.SetModelAndConfig(func() schema.LocalAIRequest { return new(schema.OpenAIRequest) }),
func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -95,6 +100,7 @@ func RegisterOpenAIRoutes(app *echo.Echo,
// embeddings
embeddingHandler := openai.EmbeddingsEndpoint(application.ModelConfigLoader(), application.ModelLoader(), application.ApplicationConfig())
embeddingMiddleware := []echo.MiddlewareFunc{
+ traceMiddleware,
re.BuildFilteredFirstAvailableDefaultModel(config.BuildUsecaseFilterFn(config.FLAG_EMBEDDINGS)),
re.BuildConstantDefaultModelNameMiddleware("gpt-4o"),
re.SetModelAndConfig(func() schema.LocalAIRequest { return new(schema.OpenAIRequest) }),
@@ -113,6 +119,7 @@ func RegisterOpenAIRoutes(app *echo.Echo,
audioHandler := openai.TranscriptEndpoint(application.ModelConfigLoader(), application.ModelLoader(), application.ApplicationConfig())
audioMiddleware := []echo.MiddlewareFunc{
+ traceMiddleware,
re.BuildFilteredFirstAvailableDefaultModel(config.BuildUsecaseFilterFn(config.FLAG_TRANSCRIPT)),
re.SetModelAndConfig(func() schema.LocalAIRequest { return new(schema.OpenAIRequest) }),
func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -130,6 +137,7 @@ func RegisterOpenAIRoutes(app *echo.Echo,
audioSpeechHandler := localai.TTSEndpoint(application.ModelConfigLoader(), application.ModelLoader(), application.ApplicationConfig())
audioSpeechMiddleware := []echo.MiddlewareFunc{
+ traceMiddleware,
re.BuildFilteredFirstAvailableDefaultModel(config.BuildUsecaseFilterFn(config.FLAG_TTS)),
re.SetModelAndConfig(func() schema.LocalAIRequest { return new(schema.TTSRequest) }),
}
@@ -140,6 +148,7 @@ func RegisterOpenAIRoutes(app *echo.Echo,
// images
imageHandler := openai.ImageEndpoint(application.ModelConfigLoader(), application.ModelLoader(), application.ApplicationConfig())
imageMiddleware := []echo.MiddlewareFunc{
+ traceMiddleware,
// Default: use the first available image generation model
re.BuildFilteredFirstAvailableDefaultModel(config.BuildUsecaseFilterFn(config.FLAG_IMAGE)),
re.SetModelAndConfig(func() schema.LocalAIRequest { return new(schema.OpenAIRequest) }),
@@ -164,6 +173,7 @@ func RegisterOpenAIRoutes(app *echo.Echo,
// videos (OpenAI-compatible endpoints mapped to LocalAI video handler)
videoHandler := openai.VideoEndpoint(application.ModelConfigLoader(), application.ModelLoader(), application.ApplicationConfig())
videoMiddleware := []echo.MiddlewareFunc{
+ traceMiddleware,
re.BuildFilteredFirstAvailableDefaultModel(config.BuildUsecaseFilterFn(config.FLAG_VIDEO)),
re.SetModelAndConfig(func() schema.LocalAIRequest { return new(schema.OpenAIRequest) }),
func(next echo.HandlerFunc) echo.HandlerFunc {
diff --git a/core/http/routes/ui_api.go b/core/http/routes/ui_api.go
index 36194d5c06c4..0aeb09a4866c 100644
--- a/core/http/routes/ui_api.go
+++ b/core/http/routes/ui_api.go
@@ -16,6 +16,7 @@ import (
"github.com/mudler/LocalAI/core/config"
"github.com/mudler/LocalAI/core/gallery"
"github.com/mudler/LocalAI/core/http/endpoints/localai"
+ "github.com/mudler/LocalAI/core/http/middleware"
"github.com/mudler/LocalAI/core/p2p"
"github.com/mudler/LocalAI/core/services"
"github.com/mudler/LocalAI/pkg/model"
@@ -947,4 +948,24 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
app.GET("/api/settings", localai.GetSettingsEndpoint(applicationInstance))
app.POST("/api/settings", localai.UpdateSettingsEndpoint(applicationInstance))
}
+
+ // Logs API
+ app.GET("/api/traces", func(c echo.Context) error {
+ if !appConfig.EnableTracing {
+ return c.JSON(503, map[string]any{
+ "error": "Tracing disabled",
+ })
+ }
+ logs := middleware.GetAPILogs()
+ return c.JSON(200, map[string]interface{}{
+ "logs": logs,
+ })
+ })
+
+ app.POST("/api/traces/clear", func(c echo.Context) error {
+ middleware.ClearAPILogs()
+ return c.JSON(200, map[string]interface{}{
+ "message": "Traces cleared",
+ })
+ })
}
From bcd7143caac37e6e957f0d349ab393af31205f05 Mon Sep 17 00:00:00 2001
From: Richard Palethorpe
Date: Wed, 17 Dec 2025 12:31:59 +0000
Subject: [PATCH 2/2] feat(traces): Add traces UI
Signed-off-by: Richard Palethorpe
---
core/cli/run.go | 6 +
core/config/application_config.go | 18 ++
core/config/runtime_settings.go | 10 +-
core/http/middleware/trace.go | 115 +++++----
core/http/routes/ui.go | 20 ++
core/http/routes/ui_api.go | 6 +-
core/http/views/partials/navbar.html | 6 +
core/http/views/settings.html | 136 +++++++----
core/http/views/traces.html | 334 +++++++++++++++++++++++++++
go.mod | 22 +-
go.sum | 46 +---
11 files changed, 549 insertions(+), 170 deletions(-)
create mode 100644 core/http/views/traces.html
diff --git a/core/cli/run.go b/core/cli/run.go
index f9b530a201bb..912bd5522b7c 100644
--- a/core/cli/run.go
+++ b/core/cli/run.go
@@ -81,6 +81,7 @@ type RunCMD struct {
MachineTag string `env:"LOCALAI_MACHINE_TAG,MACHINE_TAG" help:"Add Machine-Tag header to each response which is useful to track the machine in the P2P network" group:"api"`
LoadToMemory []string `env:"LOCALAI_LOAD_TO_MEMORY,LOAD_TO_MEMORY" help:"A list of models to load into memory at startup" group:"models"`
EnableTracing bool `env:"LOCALAI_ENABLE_TRACING,ENABLE_TRACING" help:"Enable API tracing" group:"api"`
+ TracingMaxItems int `env:"LOCALAI_TRACING_MAX_ITEMS" default:"1024" help:"Maximum number of traces to keep" group:"api"`
AgentJobRetentionDays int `env:"LOCALAI_AGENT_JOB_RETENTION_DAYS,AGENT_JOB_RETENTION_DAYS" default:"30" help:"Number of days to keep agent job history (default: 30)" group:"api"`
Version bool
@@ -157,6 +158,11 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error {
opts = append(opts, config.EnableTracing)
}
+ if r.EnableTracing {
+ opts = append(opts, config.EnableTracing)
+ }
+ opts = append(opts, config.WithTracingMaxItems(r.TracingMaxItems))
+
token := ""
if r.Peer2Peer || r.Peer2PeerToken != "" {
xlog.Info("P2P mode enabled")
diff --git a/core/config/application_config.go b/core/config/application_config.go
index d9249beeef45..26b603f53aed 100644
--- a/core/config/application_config.go
+++ b/core/config/application_config.go
@@ -20,6 +20,7 @@ type ApplicationConfig struct {
F16 bool
Debug bool
EnableTracing bool
+ TracingMaxItems int
GeneratedContentDir string
UploadDir string
@@ -98,6 +99,7 @@ func NewApplicationConfig(o ...AppOption) *ApplicationConfig {
AgentJobRetentionDays: 30, // Default: 30 days
LRUEvictionMaxRetries: 30, // Default: 30 retries
LRUEvictionRetryInterval: 1 * time.Second, // Default: 1 second
+ TracingMaxItems: 1024,
PathWithoutAuth: []string{
"/static/",
"/generated-audio/",
@@ -423,6 +425,12 @@ func WithDebug(debug bool) AppOption {
}
}
+func WithTracingMaxItems(items int) AppOption {
+ return func(o *ApplicationConfig) {
+ o.TracingMaxItems = items
+ }
+}
+
func WithGeneratedContentDir(generatedContentDir string) AppOption {
return func(o *ApplicationConfig) {
o.GeneratedContentDir = generatedContentDir
@@ -548,6 +556,8 @@ func (o *ApplicationConfig) ToRuntimeSettings() RuntimeSettings {
contextSize := o.ContextSize
f16 := o.F16
debug := o.Debug
+ tracingMaxItems := o.TracingMaxItems
+ enableTracing := o.EnableTracing
cors := o.CORS
csrf := o.CSRF
corsAllowOrigins := o.CORSAllowOrigins
@@ -604,6 +614,8 @@ func (o *ApplicationConfig) ToRuntimeSettings() RuntimeSettings {
ContextSize: &contextSize,
F16: &f16,
Debug: &debug,
+ TracingMaxItems: &tracingMaxItems,
+ EnableTracing: &enableTracing,
CORS: &cors,
CSRF: &csrf,
CORSAllowOrigins: &corsAllowOrigins,
@@ -718,6 +730,12 @@ func (o *ApplicationConfig) ApplyRuntimeSettings(settings *RuntimeSettings) (req
if settings.Debug != nil {
o.Debug = *settings.Debug
}
+ if settings.EnableTracing != nil {
+ o.EnableTracing = *settings.EnableTracing
+ }
+ if settings.TracingMaxItems != nil {
+ o.TracingMaxItems = *settings.TracingMaxItems
+ }
if settings.CORS != nil {
o.CORS = *settings.CORS
}
diff --git a/core/config/runtime_settings.go b/core/config/runtime_settings.go
index 93783511d475..1a7f6db8175c 100644
--- a/core/config/runtime_settings.go
+++ b/core/config/runtime_settings.go
@@ -32,10 +32,12 @@ type RuntimeSettings struct {
LRUEvictionRetryInterval *string `json:"lru_eviction_retry_interval,omitempty"` // Interval between retries when waiting for busy models (e.g., 1s, 2s) (default: 1s)
// Performance settings
- Threads *int `json:"threads,omitempty"`
- ContextSize *int `json:"context_size,omitempty"`
- F16 *bool `json:"f16,omitempty"`
- Debug *bool `json:"debug,omitempty"`
+ Threads *int `json:"threads,omitempty"`
+ ContextSize *int `json:"context_size,omitempty"`
+ F16 *bool `json:"f16,omitempty"`
+ Debug *bool `json:"debug,omitempty"`
+ EnableTracing *bool `json:"enable_tracing,omitempty"`
+ TracingMaxItems *int `json:"tracing_max_items,omitempty"`
// Security/CORS settings
CORS *bool `json:"cors,omitempty"`
diff --git a/core/http/middleware/trace.go b/core/http/middleware/trace.go
index d5ecd282aa86..aa63ba349f37 100644
--- a/core/http/middleware/trace.go
+++ b/core/http/middleware/trace.go
@@ -2,43 +2,40 @@ package middleware
import (
"bytes"
+ "github.com/emirpasic/gods/v2/queues/circularbuffer"
"io"
"net/http"
+ "sort"
"sync"
+ "time"
"github.com/labstack/echo/v4"
"github.com/mudler/LocalAI/core/application"
- "github.com/rs/zerolog/log"
+ "github.com/mudler/xlog"
)
+type APIExchangeRequest struct {
+ Method string `json:"method"`
+ Path string `json:"path"`
+ Headers *http.Header `json:"headers"`
+ Body *[]byte `json:"body"`
+}
+
+type APIExchangeResponse struct {
+ Status int `json:"status"`
+ Headers *http.Header `json:"headers"`
+ Body *[]byte `json:"body"`
+}
+
type APIExchange struct {
- Request struct {
- Method string
- Path string
- Headers http.Header
- Body []byte
- }
- Response struct {
- Status int
- Headers http.Header
- Body []byte
- }
+ Timestamp time.Time `json:"timestamp"`
+ Request APIExchangeRequest `json:"request"`
+ Response APIExchangeResponse `json:"response"`
}
-var apiLogs []APIExchange
+var traceBuffer *circularbuffer.Queue[APIExchange]
var mu sync.Mutex
-var logChan = make(chan APIExchange, 100) // Buffered channel for serialization
-
-func init() {
- go func() {
- for exchange := range logChan {
- mu.Lock()
- apiLogs = append(apiLogs, exchange)
- mu.Unlock()
- log.Debug().Msgf("Logged exchange: %s %s - Status: %d", exchange.Request.Method, exchange.Request.Path, exchange.Response.Status)
- }
- }()
-}
+var logChan = make(chan APIExchange, 100)
type bodyWriter struct {
http.ResponseWriter
@@ -58,26 +55,39 @@ func (w *bodyWriter) Flush() {
// TraceMiddleware intercepts and logs JSON API requests and responses
func TraceMiddleware(app *application.Application) echo.MiddlewareFunc {
+ if app.ApplicationConfig().EnableTracing && traceBuffer == nil {
+ traceBuffer = circularbuffer.New[APIExchange](app.ApplicationConfig().TracingMaxItems)
+
+ go func() {
+ for exchange := range logChan {
+ mu.Lock()
+ traceBuffer.Enqueue(exchange)
+ mu.Unlock()
+ }
+ }()
+ }
+
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if !app.ApplicationConfig().EnableTracing {
return next(c)
}
- // Only log if Content-Type is application/json
if c.Request().Header.Get("Content-Type") != "application/json" {
return next(c)
}
body, err := io.ReadAll(c.Request().Body)
if err != nil {
- log.Error().Err(err).Msg("Failed to read request body")
+ xlog.Error("Failed to read request body")
return err
}
// Restore the body for downstream handlers
c.Request().Body = io.NopCloser(bytes.NewBuffer(body))
+ startTime := time.Now()
+
// Wrap response writer to capture body
resBody := new(bytes.Buffer)
mw := &bodyWriter{
@@ -93,34 +103,31 @@ func TraceMiddleware(app *application.Application) echo.MiddlewareFunc {
}
// Create exchange log
+ requestHeaders := c.Request().Header.Clone()
+ requestBody := make([]byte, len(body))
+ copy(requestBody, body)
+ responseHeaders := c.Response().Header().Clone()
+ responseBody := make([]byte, resBody.Len())
+ copy(responseBody, resBody.Bytes())
exchange := APIExchange{
- Request: struct {
- Method string
- Path string
- Headers http.Header
- Body []byte
- }{
+ Timestamp: startTime,
+ Request: APIExchangeRequest{
Method: c.Request().Method,
Path: c.Path(),
- Headers: c.Request().Header.Clone(),
- Body: body,
+ Headers: &requestHeaders,
+ Body: &requestBody,
},
- Response: struct {
- Status int
- Headers http.Header
- Body []byte
- }{
+ Response: APIExchangeResponse{
Status: c.Response().Status,
- Headers: c.Response().Header().Clone(),
- Body: resBody.Bytes(),
+ Headers: &responseHeaders,
+ Body: &responseBody,
},
}
- // Send to channel (non-blocking)
select {
case logChan <- exchange:
default:
- log.Warn().Msg("API log channel full, dropping log")
+ xlog.Warn("Trace channel full, dropping trace")
}
return nil
@@ -128,16 +135,22 @@ func TraceMiddleware(app *application.Application) echo.MiddlewareFunc {
}
}
-// GetAPILogs returns a copy of the logged API exchanges for display
-func GetAPILogs() []APIExchange {
+// GetTraces returns a copy of the logged API exchanges for display
+func GetTraces() []APIExchange {
mu.Lock()
- defer mu.Unlock()
- return append([]APIExchange{}, apiLogs...)
+ traces := traceBuffer.Values()
+ mu.Unlock()
+
+ sort.Slice(traces, func(i, j int) bool {
+ return traces[i].Timestamp.Before(traces[j].Timestamp)
+ })
+
+ return traces
}
-// ClearAPILogs clears the in-memory logs
-func ClearAPILogs() {
+// ClearTraces clears the in-memory logs
+func ClearTraces() {
mu.Lock()
- apiLogs = nil
+ traceBuffer.Clear()
mu.Unlock()
}
diff --git a/core/http/routes/ui.go b/core/http/routes/ui.go
index 4b852875fa77..da6f5d1ee7f5 100644
--- a/core/http/routes/ui.go
+++ b/core/http/routes/ui.go
@@ -317,4 +317,24 @@ func RegisterUIRoutes(app *echo.Echo,
// Render index
return c.Render(200, "views/tts", summary)
})
+
+ // Traces UI
+ app.GET("/traces", func(c echo.Context) error {
+ summary := map[string]interface{}{
+ "Title": "LocalAI - Traces",
+ "BaseURL": middleware.BaseURL(c),
+ "Version": internal.PrintableVersion(),
+ }
+ return c.Render(200, "views/traces", summary)
+ })
+
+ app.GET("/api/traces", func(c echo.Context) error {
+ return c.JSON(200, middleware.GetTraces())
+ })
+
+ app.POST("/api/traces/clear", func(c echo.Context) error {
+ middleware.ClearTraces()
+ return c.NoContent(204)
+ })
+
}
diff --git a/core/http/routes/ui_api.go b/core/http/routes/ui_api.go
index 0aeb09a4866c..84af2e32fe57 100644
--- a/core/http/routes/ui_api.go
+++ b/core/http/routes/ui_api.go
@@ -956,14 +956,14 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
"error": "Tracing disabled",
})
}
- logs := middleware.GetAPILogs()
+ traces := middleware.GetTraces()
return c.JSON(200, map[string]interface{}{
- "logs": logs,
+ "traces": traces,
})
})
app.POST("/api/traces/clear", func(c echo.Context) error {
- middleware.ClearAPILogs()
+ middleware.ClearTraces()
return c.JSON(200, map[string]interface{}{
"message": "Traces cleared",
})
diff --git a/core/http/views/partials/navbar.html b/core/http/views/partials/navbar.html
index 2afdfa36e13f..cbc092c41a04 100644
--- a/core/http/views/partials/navbar.html
+++ b/core/http/views/partials/navbar.html
@@ -37,6 +37,9 @@
Agent Jobs
+
+ Traces
+
API
@@ -94,6 +97,9 @@
Agent Jobs
+
+ Traces
+
API
diff --git a/core/http/views/settings.html b/core/http/views/settings.html
index 1ad960430169..97587f0e3406 100644
--- a/core/http/views/settings.html
+++ b/core/http/views/settings.html
@@ -10,7 +10,7 @@
-
@@ -93,7 +93,7 @@ Idle Timeout
Time before an idle backend is stopped (e.g., 15m, 1h)
- Automatically stop backends that are busy for too long (stuck processes)
-
@@ -118,7 +118,7 @@ Busy Timeout
Time before a busy backend is stopped (e.g., 5m, 30m)
- Check Interval
How often the watchdog checks backends and memory usage (e.g., 2s, 30s)
-
-
@@ -206,7 +206,7 @@
System RAM
-
@@ -223,7 +223,7 @@ Evict backends when memory usage exceeds threshold
-
@@ -235,12 +235,12 @@ Memory Threshold (%)
When memory usage exceeds this, backends will be evicted (50-100%)
-
-
@@ -263,7 +263,7 @@ Max Active Backends
Maximum number of models to keep loaded at once (0 = unlimited, 1 = single backend mode). Least recently used models are evicted when limit is reached.
- Enable backends to handle multiple requests in parallel (if supported)
-
@@ -300,7 +300,7 @@ Default Threads
Number of threads to use for model inference (0 = auto)
-
@@ -310,7 +310,7 @@ Default Context Size
Default context window size for models
-
@@ -323,7 +323,7 @@ Use 16-bit floating point precision
-
@@ -336,11 +336,37 @@ Enable debug logging
-
+
+
+
+
+
Enable Tracing
+
Enable tracing of requests and responses
+
+
+
+
+
+
+
+
+
+
Tracing Max Items
+
Maximum number of tracing items to keep
+
+
+
@@ -362,7 +388,7 @@ Enable Cross-Origin Resource Sharing
-
@@ -372,7 +398,7 @@ CORS Allow Origins
Comma-separated list of allowed origins
-
@@ -384,7 +410,7 @@ Enable Cross-Site Request Forgery protection
-
@@ -407,7 +433,7 @@ P2P Token
Authentication token for P2P network (set to 0 to generate a new token)
-
@@ -416,7 +442,7 @@ P2P Network ID
Network identifier for P2P connections
-
@@ -428,7 +454,7 @@ Enable federated instance mode
-
@@ -451,7 +477,7 @@ Job Retention Days
Number of days to keep job history (default: 30)
-
@@ -474,7 +500,7 @@ API Keys
List of API keys (one per line or comma-separated)
-
@@ -501,7 +527,7 @@ Automatically load model galleries on startup
-
@@ -514,7 +540,7 @@ Automatically load backend galleries on startup
-
@@ -524,7 +550,7 @@ Model Galleries (JSON)
Array of gallery objects with 'url' and 'name' fields
-
@@ -534,7 +560,7 @@ Backend Galleries (JSON)
Array of backend gallery objects with 'url' and 'name' fields
-
@@ -558,7 +584,7 @@
-
@@ -594,6 +620,8 @@
+
+{{template "views/partials/head" .}}
+
+
+
+
+ {{template "views/partials/navbar" .}}
+
+
+
+
+
+
+
+
+
+
+ API Traces
+
+
View logged API requests and responses
+
+
+
+
+
+
+
+
+ Tracing Settings
+
+
Configure API tracing
+
+
+
+
+
+
Enable Tracing
+
Enable tracing of requests and responses
+
+
+
+
+
+
+
+
+
+
Tracing Max Items
+
Maximum number of tracing items to keep (0 = unlimited)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Method
+ Path
+ Status
+ Actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Trace Details
+
+
+
+
+
+
+
+
+
+
+ {{template "views/partials/footer" .}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/go.mod b/go.mod
index 1898f3f11b46..4fd0bc819e72 100644
--- a/go.mod
+++ b/go.mod
@@ -9,22 +9,17 @@ require (
fyne.io/fyne/v2 v2.7.1
github.com/Masterminds/sprig/v3 v3.3.0
github.com/alecthomas/kong v1.13.0
- github.com/census-instrumentation/opencensus-proto v0.2.1
github.com/charmbracelet/glamour v0.10.0
- github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f
github.com/containerd/containerd v1.7.30
github.com/ebitengine/purego v0.9.1
+ github.com/emirpasic/gods/v2 v2.0.0-alpha
github.com/fsnotify/fsnotify v1.9.0
github.com/go-audio/wav v1.1.0
github.com/go-skynet/go-llama.cpp v0.0.0-20240314183750-6a8041ef6b46
- github.com/gofiber/fiber/v2 v2.52.10
- github.com/gofiber/websocket/v2 v2.2.1
github.com/gofrs/flock v0.13.0
- github.com/golang/protobuf v1.5.4
github.com/google/go-containerregistry v0.20.7
github.com/google/uuid v1.6.0
github.com/gpustack/gguf-parser-go v0.22.1
- github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/hpcloud/tail v1.0.0
github.com/ipfs/go-log v1.0.5
github.com/jaypipes/ghw v0.21.2
@@ -62,7 +57,6 @@ require (
go.opentelemetry.io/otel/exporters/prometheus v0.61.0
go.opentelemetry.io/otel/metric v1.39.0
go.opentelemetry.io/otel/sdk/metric v1.39.0
- google.golang.org/api v0.218.0
google.golang.org/grpc v1.77.0
google.golang.org/protobuf v1.36.10
gopkg.in/yaml.v2 v2.4.0
@@ -71,24 +65,10 @@ require (
)
require (
- cel.dev/expr v0.24.0 // indirect
- cloud.google.com/go/auth v0.14.0 // indirect
- cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
- cloud.google.com/go/compute/metadata v0.9.0 // indirect
- github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
- github.com/fasthttp/websocket v1.5.3 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
- github.com/google/s2a-go v0.1.9 // indirect
- github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
- github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/labstack/gommon v0.4.2 // indirect
- github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/swaggo/files/v2 v2.0.2 // indirect
- github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
- github.com/valyala/tcplisten v1.0.0 // indirect
- google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect
)
require (
diff --git a/go.sum b/go.sum
index d43bef92d600..1764f032105c 100644
--- a/go.sum
+++ b/go.sum
@@ -1,15 +1,7 @@
-cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
-cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
-cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM=
-cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A=
-cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
-cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
-cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
-cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
@@ -52,7 +44,6 @@ github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
-github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
@@ -70,7 +61,6 @@ github.com/c-robinson/iplib v1.0.8 h1:exDRViDyL9UBLcfmlxxkY5odWX5092nPsQIykHXhIn
github.com/c-robinson/iplib v1.0.8/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
-github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -94,8 +84,6 @@ github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7m
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=
-github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/containerd/containerd v1.7.30 h1:/2vezDpLDVGGmkUXmlNPLCCNKHJ5BbC5tJB5JNzQhqE=
@@ -154,14 +142,12 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
+github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
-github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
-github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek=
-github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@@ -237,10 +223,6 @@ github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gofiber/fiber/v2 v2.52.10 h1:jRHROi2BuNti6NYXmZ6gbNSfT3zj/8c0xy94GOU5elY=
-github.com/gofiber/fiber/v2 v2.52.10/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
-github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
-github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -256,7 +238,6 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -295,17 +276,11 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
-github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
-github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
-github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
-github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -318,7 +293,6 @@ github.com/gpustack/gguf-parser-go v0.22.1/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowg
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
@@ -691,7 +665,6 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
-github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
@@ -703,8 +676,6 @@ github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU
github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM=
github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
-github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
-github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -810,12 +781,8 @@ github.com/ulikunitz/xz v0.5.14/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
-github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
-github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
@@ -945,7 +912,6 @@ golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -963,7 +929,6 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
@@ -986,7 +951,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1070,8 +1034,6 @@ gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
-google.golang.org/api v0.218.0 h1:x6JCjEWeZ9PFCRe9z0FBrNwj7pB7DOAqT35N+IPnAUA=
-google.golang.org/api v0.218.0/go.mod h1:5VGHBAkxrA/8EFjLVEYmMUJ8/8+gWWQ3s4cFH0FxG2M=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1082,10 +1044,8 @@ google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
-google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
@@ -1097,7 +1057,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
@@ -1124,7 +1083,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=