fix: resolve retired/preview gemini model routing and test configuration errors
This commit is contained in:
@@ -16,3 +16,5 @@ server.pid
|
|||||||
/target
|
/target
|
||||||
nohup.out
|
nohup.out
|
||||||
*.bak
|
*.bak
|
||||||
|
.antigravitycli/
|
||||||
|
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ Pre-seeded groups:
|
|||||||
| `dispatcher` | - | classifier | fast-flow, standard-pro, heavy-logic | Auto-dispatches by complexity |
|
| `dispatcher` | - | classifier | fast-flow, standard-pro, heavy-logic | Auto-dispatches by complexity |
|
||||||
| `deepseek-auto` | - | heuristic | deepseek-chat, deepseek-reasoner | Legacy provider group |
|
| `deepseek-auto` | - | heuristic | deepseek-chat, deepseek-reasoner | Legacy provider group |
|
||||||
| `openai-auto` | - | heuristic | gpt-4o-mini, gpt-4o | Legacy provider group |
|
| `openai-auto` | - | heuristic | gpt-4o-mini, gpt-4o | Legacy provider group |
|
||||||
| `gemini-auto` | - | heuristic | gemini-2.0-flash, gemini-2.5-pro | Legacy provider group |
|
| `gemini-auto` | - | heuristic | gemini-2.5-flash, gemini-2.5-pro | Legacy provider group |
|
||||||
|
|
||||||
### Image Generation (DALL-E / Imagen)
|
### Image Generation (DALL-E / Imagen)
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Define the service name/path for easy updates
|
set -e
|
||||||
|
|
||||||
BINARY_NAME="gophergate"
|
BINARY_NAME="gophergate"
|
||||||
SOURCE_PATH="./cmd/gophergate/main.go"
|
SOURCE_PATH="./cmd/gophergate/main.go"
|
||||||
|
|
||||||
echo "Stopping existing $BINARY_NAME processes..."
|
|
||||||
# Using pkill; || true ensures the script continues even if no process was found
|
|
||||||
pkill -9 "$BINARY_NAME" || echo "No running process found."
|
|
||||||
|
|
||||||
echo "Pulling latest changes from git..."
|
echo "Pulling latest changes from git..."
|
||||||
|
git stash || true
|
||||||
git pull
|
git pull
|
||||||
|
|
||||||
echo "Building the application..."
|
echo "Building the application..."
|
||||||
if go build -o "$BINARY_NAME" "$SOURCE_PATH"; then
|
go build -o "$BINARY_NAME" "$SOURCE_PATH"
|
||||||
echo "Build successful. Starting $BINARY_NAME in the background..."
|
|
||||||
# Launch with nohup and redirect output to a log file
|
echo "Restarting service..."
|
||||||
nohup "./$BINARY_NAME" > gophergate.log 2>&1 &
|
systemctl restart gophergate
|
||||||
echo "Service started. PID: $!"
|
|
||||||
|
sleep 2
|
||||||
|
if systemctl is-active --quiet gophergate; then
|
||||||
|
echo "Deploy complete. Service is running."
|
||||||
|
systemctl status gophergate --no-pager | head -5
|
||||||
else
|
else
|
||||||
echo "Build failed! Keeping the previous state."
|
echo "Service failed to start! Check: journalctl -u gophergate -n 20"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func Load() (*Config, error) {
|
|||||||
|
|
||||||
v.SetDefault("providers.gemini.api_key_env", "GEMINI_API_KEY")
|
v.SetDefault("providers.gemini.api_key_env", "GEMINI_API_KEY")
|
||||||
v.SetDefault("providers.gemini.base_url", "https://generativelanguage.googleapis.com/v1")
|
v.SetDefault("providers.gemini.base_url", "https://generativelanguage.googleapis.com/v1")
|
||||||
v.SetDefault("providers.gemini.default_model", "gemini-2.0-flash")
|
v.SetDefault("providers.gemini.default_model", "gemini-2.5-flash")
|
||||||
v.SetDefault("providers.gemini.enabled", true)
|
v.SetDefault("providers.gemini.enabled", true)
|
||||||
|
|
||||||
v.SetDefault("providers.deepseek.api_key_env", "DEEPSEEK_API_KEY")
|
v.SetDefault("providers.deepseek.api_key_env", "DEEPSEEK_API_KEY")
|
||||||
@@ -116,7 +116,7 @@ func Load() (*Config, error) {
|
|||||||
|
|
||||||
v.SetDefault("providers.moonshot.api_key_env", "MOONSHOT_API_KEY")
|
v.SetDefault("providers.moonshot.api_key_env", "MOONSHOT_API_KEY")
|
||||||
v.SetDefault("providers.moonshot.base_url", "https://api.moonshot.ai/v1")
|
v.SetDefault("providers.moonshot.base_url", "https://api.moonshot.ai/v1")
|
||||||
v.SetDefault("providers.moonshot.default_model", "kimi-k2.5")
|
v.SetDefault("providers.moonshot.default_model", "kimi-k2.7-code")
|
||||||
v.SetDefault("providers.moonshot.enabled", true)
|
v.SetDefault("providers.moonshot.enabled", true)
|
||||||
|
|
||||||
v.SetDefault("providers.grok.api_key_env", "GROK_API_KEY")
|
v.SetDefault("providers.grok.api_key_env", "GROK_API_KEY")
|
||||||
|
|||||||
+1
-1
@@ -209,7 +209,7 @@ func (db *DB) RunMigrations() error {
|
|||||||
}{
|
}{
|
||||||
{"deepseek-auto", "heuristic", `["deepseek-chat","deepseek-reasoner"]`, "", nil, nil, nil},
|
{"deepseek-auto", "heuristic", `["deepseek-chat","deepseek-reasoner"]`, "", nil, nil, nil},
|
||||||
{"openai-auto", "heuristic", `["gpt-4o-mini","gpt-4o"]`, "", nil, nil, nil},
|
{"openai-auto", "heuristic", `["gpt-4o-mini","gpt-4o"]`, "", nil, nil, nil},
|
||||||
{"gemini-auto", "heuristic", `["gemini-2.0-flash","gemini-2.5-pro"]`, "", nil, nil, nil},
|
{"gemini-auto", "heuristic", `["gemini-2.5-flash","gemini-2.5-pro"]`, "", nil, nil, nil},
|
||||||
{"heavy-logic", "heuristic", `["grok-4.3","kimi-k2.6","deepseek-v4-pro"]`, "", nil, intPtr(9), strPtr("Complex Coding, Logic, Agents.")},
|
{"heavy-logic", "heuristic", `["grok-4.3","kimi-k2.6","deepseek-v4-pro"]`, "", nil, intPtr(9), strPtr("Complex Coding, Logic, Agents.")},
|
||||||
{"standard-pro", "heuristic", `["gpt-5.4-mini","gemini-3-flash-preview"]`, "", nil, intPtr(5), strPtr("General Assistant, Long Docs.")},
|
{"standard-pro", "heuristic", `["gpt-5.4-mini","gemini-3-flash-preview"]`, "", nil, intPtr(5), strPtr("General Assistant, Long Docs.")},
|
||||||
{"fast-flow", "heuristic", `["deepseek-v4-flash","gpt-5.4-nano"]`, "", nil, intPtr(2), strPtr("Classification, JSON, Basic Q&A.")},
|
{"fast-flow", "heuristic", `["deepseek-v4-flash","gpt-5.4-nano"]`, "", nil, intPtr(2), strPtr("Classification, JSON, Basic Q&A.")},
|
||||||
|
|||||||
@@ -201,6 +201,16 @@ func (p *GeminiProvider) ResponsesStream(ctx context.Context, req *models.Respon
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.UnifiedRequest) (*models.ChatCompletionResponse, error) {
|
func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.UnifiedRequest) (*models.ChatCompletionResponse, error) {
|
||||||
|
// Map deprecated or preview model names to active equivalents
|
||||||
|
switch req.Model {
|
||||||
|
case "gemini-2.0-flash":
|
||||||
|
req.Model = "gemini-2.5-flash"
|
||||||
|
case "gemini-3-flash":
|
||||||
|
req.Model = "gemini-3-flash-preview"
|
||||||
|
case "gemini-3-pro":
|
||||||
|
req.Model = "gemini-3-pro-preview"
|
||||||
|
}
|
||||||
|
|
||||||
// Gemini mapping
|
// Gemini mapping
|
||||||
var contents []GeminiContent
|
var contents []GeminiContent
|
||||||
|
|
||||||
@@ -354,7 +364,10 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified
|
|||||||
|
|
||||||
baseURL := p.config.BaseURL
|
baseURL := p.config.BaseURL
|
||||||
lowerModel := strings.ToLower(req.Model)
|
lowerModel := strings.ToLower(req.Model)
|
||||||
if strings.Contains(lowerModel, "preview") || strings.Contains(lowerModel, "3.1") || strings.Contains(lowerModel, "2.0") || strings.Contains(lowerModel, "thinking") || hasMappedTools {
|
if strings.Contains(lowerModel, "preview") ||
|
||||||
|
strings.Contains(lowerModel, "thinking") ||
|
||||||
|
strings.Contains(lowerModel, "gemini-") ||
|
||||||
|
hasMappedTools {
|
||||||
// Use v1beta for preview, newer models, or when using tools
|
// Use v1beta for preview, newer models, or when using tools
|
||||||
if !strings.Contains(baseURL, "v1beta") {
|
if !strings.Contains(baseURL, "v1beta") {
|
||||||
baseURL = strings.Replace(baseURL, "/v1", "/v1beta", 1)
|
baseURL = strings.Replace(baseURL, "/v1", "/v1beta", 1)
|
||||||
@@ -468,6 +481,16 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.UnifiedRequest) (<-chan *models.ChatCompletionStreamResponse, error) {
|
func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.UnifiedRequest) (<-chan *models.ChatCompletionStreamResponse, error) {
|
||||||
|
// Map deprecated or preview model names to active equivalents
|
||||||
|
switch req.Model {
|
||||||
|
case "gemini-2.0-flash":
|
||||||
|
req.Model = "gemini-2.5-flash"
|
||||||
|
case "gemini-3-flash":
|
||||||
|
req.Model = "gemini-3-flash-preview"
|
||||||
|
case "gemini-3-pro":
|
||||||
|
req.Model = "gemini-3-pro-preview"
|
||||||
|
}
|
||||||
|
|
||||||
// Simplified Gemini mapping
|
// Simplified Gemini mapping
|
||||||
var contents []GeminiContent
|
var contents []GeminiContent
|
||||||
for i := 0; i < len(req.Messages); i++ {
|
for i := 0; i < len(req.Messages); i++ {
|
||||||
@@ -598,7 +621,10 @@ func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.U
|
|||||||
|
|
||||||
baseURL := p.config.BaseURL
|
baseURL := p.config.BaseURL
|
||||||
lowerModel := strings.ToLower(req.Model)
|
lowerModel := strings.ToLower(req.Model)
|
||||||
if strings.Contains(lowerModel, "preview") || strings.Contains(lowerModel, "3.1") || strings.Contains(lowerModel, "2.0") || strings.Contains(lowerModel, "thinking") || hasMappedTools {
|
if strings.Contains(lowerModel, "preview") ||
|
||||||
|
strings.Contains(lowerModel, "thinking") ||
|
||||||
|
strings.Contains(lowerModel, "gemini-") ||
|
||||||
|
hasMappedTools {
|
||||||
// Use v1beta for preview, newer models, or when using tools
|
// Use v1beta for preview, newer models, or when using tools
|
||||||
if !strings.Contains(baseURL, "v1beta") {
|
if !strings.Contains(baseURL, "v1beta") {
|
||||||
baseURL = strings.Replace(baseURL, "/v1", "/v1beta", 1)
|
baseURL = strings.Replace(baseURL, "/v1", "/v1beta", 1)
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ func (p *MoonshotProvider) ChatCompletion(ctx context.Context, req *models.Unifi
|
|||||||
body["max_completion_tokens"] = maxTokens
|
body["max_completion_tokens"] = maxTokens
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if strings.Contains(strings.ToLower(req.Model), "kimi-k2.6") {
|
||||||
|
if _, ok := body["temperature"]; ok {
|
||||||
|
body["temperature"] = 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
baseURL := strings.TrimRight(p.config.BaseURL, "/")
|
baseURL := strings.TrimRight(p.config.BaseURL, "/")
|
||||||
|
|
||||||
@@ -90,6 +95,11 @@ func (p *MoonshotProvider) ChatCompletionStream(ctx context.Context, req *models
|
|||||||
body["max_completion_tokens"] = maxTokens
|
body["max_completion_tokens"] = maxTokens
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if strings.Contains(strings.ToLower(req.Model), "kimi-k2.6") {
|
||||||
|
if _, ok := body["temperature"]; ok {
|
||||||
|
body["temperature"] = 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
baseURL := strings.TrimRight(p.config.BaseURL, "/")
|
baseURL := strings.TrimRight(p.config.BaseURL, "/")
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,13 @@ type ConditionRule struct {
|
|||||||
|
|
||||||
// Conditions defines the matching parameters for a rule.
|
// Conditions defines the matching parameters for a rule.
|
||||||
type Conditions struct {
|
type Conditions struct {
|
||||||
AnyOfTags []string `json:"any_of_tags,omitempty"`
|
AnyOfTags []string `json:"any_of_tags,omitempty"`
|
||||||
MaxInputTokensLt *int `json:"max_input_tokens_lt,omitempty"`
|
MaxInputTokensLt *int `json:"max_input_tokens_lt,omitempty"`
|
||||||
RequiresReasoning *bool `json:"requires_reasoning,omitempty"`
|
RequiresReasoning *bool `json:"requires_reasoning,omitempty"`
|
||||||
RequiresToolCalling *bool `json:"requires_tool_calling,omitempty"`
|
RequiresToolCalling *bool `json:"requires_tool_calling,omitempty"`
|
||||||
HasMultimodalInput *bool `json:"has_multimodal_input,omitempty"`
|
IsSoftwareDevelopment *bool `json:"is_software_development,omitempty"`
|
||||||
IsDefaultFallback *bool `json:"is_default_fallback,omitempty"`
|
HasMultimodalInput *bool `json:"has_multimodal_input,omitempty"`
|
||||||
|
IsDefaultFallback *bool `json:"is_default_fallback,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func routeHeuristic(group db.ModelGroup, targets []string, routeCtx *RouteContext) (*Decision, error) {
|
func routeHeuristic(group db.ModelGroup, targets []string, routeCtx *RouteContext) (*Decision, error) {
|
||||||
@@ -198,6 +199,13 @@ func matchConditions(cond Conditions, routeCtx *RouteContext) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check software development flag
|
||||||
|
if cond.IsSoftwareDevelopment != nil {
|
||||||
|
if routeCtx.IsSoftwareDevelopment != *cond.IsSoftwareDevelopment {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check multimodal flag
|
// Check multimodal flag
|
||||||
if cond.HasMultimodalInput != nil {
|
if cond.HasMultimodalInput != nil {
|
||||||
if routeCtx.HasMultimodalInput != *cond.HasMultimodalInput {
|
if routeCtx.HasMultimodalInput != *cond.HasMultimodalInput {
|
||||||
|
|||||||
@@ -140,3 +140,65 @@ func TestRouteHeuristic_LegacyRules(t *testing.T) {
|
|||||||
t.Fatalf("expected kimi-k2.6, got %s", dec2.SelectedModel)
|
t.Fatalf("expected kimi-k2.6, got %s", dec2.SelectedModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteHeuristic_SoftwareDevelopmentCondition(t *testing.T) {
|
||||||
|
targets := []string{
|
||||||
|
"gemini-3-flash",
|
||||||
|
"kimi-k2.7-code",
|
||||||
|
"deepseek-v4-pro",
|
||||||
|
"mimo-v2.5-pro",
|
||||||
|
}
|
||||||
|
|
||||||
|
rulesJSON := `[
|
||||||
|
{
|
||||||
|
"rule_id": "agentic_code_and_tools",
|
||||||
|
"conditions": {
|
||||||
|
"requires_tool_calling": true,
|
||||||
|
"is_software_development": true
|
||||||
|
},
|
||||||
|
"primary_model": "kimi-k2.7-code",
|
||||||
|
"fallback_model": "deepseek-v4-pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule_id": "agentic_general_tasks",
|
||||||
|
"conditions": {
|
||||||
|
"requires_tool_calling": true
|
||||||
|
},
|
||||||
|
"primary_model": "mimo-v2.5-pro"
|
||||||
|
}
|
||||||
|
]`
|
||||||
|
|
||||||
|
group := db.ModelGroup{
|
||||||
|
ID: "dustins_stack",
|
||||||
|
Strategy: "heuristic",
|
||||||
|
HeuristicRules: &rulesJSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Tool calling + Software dev should route to kimi-k2.7-code
|
||||||
|
ctx1 := &RouteContext{
|
||||||
|
UserMessage: "Write a python script to parse logs",
|
||||||
|
RequiresToolCalling: true,
|
||||||
|
IsSoftwareDevelopment: true,
|
||||||
|
}
|
||||||
|
dec1, err := routeHeuristic(group, targets, ctx1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if dec1.SelectedModel != "kimi-k2.7-code" {
|
||||||
|
t.Fatalf("expected kimi-k2.7-code, got %s", dec1.SelectedModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Tool calling but NOT Software dev should route to mimo-v2.5-pro
|
||||||
|
ctx2 := &RouteContext{
|
||||||
|
UserMessage: "Search the web for weather in New York",
|
||||||
|
RequiresToolCalling: true,
|
||||||
|
IsSoftwareDevelopment: false,
|
||||||
|
}
|
||||||
|
dec2, err := routeHeuristic(group, targets, ctx2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if dec2.SelectedModel != "mimo-v2.5-pro" {
|
||||||
|
t.Fatalf("expected mimo-v2.5-pro, got %s (reason: %s)", dec2.SelectedModel, dec2.Reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,12 +18,13 @@ type Decision struct {
|
|||||||
|
|
||||||
// RouteContext holds metadata of the request to evaluate condition rules.
|
// RouteContext holds metadata of the request to evaluate condition rules.
|
||||||
type RouteContext struct {
|
type RouteContext struct {
|
||||||
UserMessage string `json:"user_message"`
|
UserMessage string `json:"user_message"`
|
||||||
InputTokens int `json:"input_tokens"`
|
InputTokens int `json:"input_tokens"`
|
||||||
HasMultimodalInput bool `json:"has_multimodal_input"`
|
HasMultimodalInput bool `json:"has_multimodal_input"`
|
||||||
RequiresToolCalling bool `json:"requires_tool_calling"`
|
RequiresToolCalling bool `json:"requires_tool_calling"`
|
||||||
RequiresReasoning bool `json:"requires_reasoning"`
|
RequiresReasoning bool `json:"requires_reasoning"`
|
||||||
Tags []string `json:"tags"`
|
IsSoftwareDevelopment bool `json:"is_software_development"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClassifierFunc is the callback for classifier-based routing.
|
// ClassifierFunc is the callback for classifier-based routing.
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ func (s *Server) handleGetModels(c *gin.Context) {
|
|||||||
|
|
||||||
var result []gin.H
|
var result []gin.H
|
||||||
s.registryMu.RLock()
|
s.registryMu.RLock()
|
||||||
|
defer s.registryMu.RUnlock()
|
||||||
if s.registry != nil {
|
if s.registry != nil {
|
||||||
for pID, pInfo := range s.registry.Providers {
|
for pID, pInfo := range s.registry.Providers {
|
||||||
proxyProvider, allowed := allowedRegistryProviders[pID]
|
proxyProvider, allowed := allowedRegistryProviders[pID]
|
||||||
@@ -208,6 +209,7 @@ func (s *Server) handleUpdateModel(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.registryMu.RUnlock()
|
||||||
|
|
||||||
_, err := s.database.Exec(`
|
_, err := s.database.Exec(`
|
||||||
INSERT INTO model_configs (id, provider_id, enabled, prompt_cost_per_m, completion_cost_per_m, cache_read_cost_per_m, cache_write_cost_per_m, mapping)
|
INSERT INTO model_configs (id, provider_id, enabled, prompt_cost_per_m, completion_cost_per_m, cache_read_cost_per_m, cache_write_cost_per_m, mapping)
|
||||||
|
|||||||
@@ -226,11 +226,11 @@ func (s *Server) handleTestProvider(c *gin.Context) {
|
|||||||
|
|
||||||
// Adjust model for non-openai providers
|
// Adjust model for non-openai providers
|
||||||
if name == "gemini" {
|
if name == "gemini" {
|
||||||
testReq.Model = "gemini-2.0-flash"
|
testReq.Model = "gemini-2.5-flash"
|
||||||
} else if name == "deepseek" {
|
} else if name == "deepseek" {
|
||||||
testReq.Model = "deepseek-chat"
|
testReq.Model = "deepseek-chat"
|
||||||
} else if name == "moonshot" {
|
} else if name == "moonshot" {
|
||||||
testReq.Model = "kimi-k2.5"
|
testReq.Model = "kimi-k2.7-code"
|
||||||
} else if name == "grok" {
|
} else if name == "grok" {
|
||||||
testReq.Model = "grok-4-1-fast-non-reasoning"
|
testReq.Model = "grok-4-1-fast-non-reasoning"
|
||||||
} else if name == "xiaomi" {
|
} else if name == "xiaomi" {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIsSoftwareDevelopment(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
message string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"can you check the logs? it looks like a lot of requests are being routed to kimi-k2.7-code when they don't need to be", false},
|
||||||
|
{"it looks like its still routing to kimi when it shouldn't need to", false},
|
||||||
|
{"Write a python script to parse logs", true},
|
||||||
|
{"Search the web for weather in New York", false},
|
||||||
|
{"How to build a compiler in Go", true},
|
||||||
|
{"Check my vscode config", false},
|
||||||
|
{"Let's decode this barcode", false},
|
||||||
|
{"go ahead and email both docxs and the ppt to kayla\n\n<memory-context>... HELPipedia_Telethon_Speaker_Script.docx ... mental health program specialist ...", false},
|
||||||
|
{"academic program development", false},
|
||||||
|
{"write a script for the video presentation", false},
|
||||||
|
{"How to write a bash script", true},
|
||||||
|
{"refactor this sql query", true},
|
||||||
|
{"can you organize Downloads and Documents real quick?\n\n<memory-context>\nCLI wrapper: ~/Projects/rag-engine/rag (bash script activating .venv). Python venv@~/Projects/rag-engine/.venv/.\n</memory-context>", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
result := isSoftwareDevelopment(tt.message)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("isSoftwareDevelopment(%q) = %v; expected %v", tt.message, result, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
-11
@@ -1,12 +1,13 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -57,7 +58,9 @@ func NewServer(cfg *config.Config, database *db.DB) *Server {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Warning: Failed to fetch initial model registry: %v\n", err)
|
fmt.Printf("Warning: Failed to fetch initial model registry: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
|
s.registryMu.Lock()
|
||||||
s.registry = registry
|
s.registry = registry
|
||||||
|
s.registryMu.Unlock()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -976,12 +979,14 @@ func (s *Server) buildRouteContextFromChat(req models.ChatCompletionRequest) *ro
|
|||||||
strings.Contains(msgLower, "explain in detail")
|
strings.Contains(msgLower, "explain in detail")
|
||||||
|
|
||||||
routeCtx := &router.RouteContext{
|
routeCtx := &router.RouteContext{
|
||||||
UserMessage: userMessage,
|
UserMessage: userMessage,
|
||||||
InputTokens: inputTokens,
|
InputTokens: inputTokens,
|
||||||
HasMultimodalInput: hasMultimodal,
|
HasMultimodalInput: hasMultimodal,
|
||||||
RequiresToolCalling: requiresToolCalling,
|
RequiresToolCalling: requiresToolCalling,
|
||||||
RequiresReasoning: requiresReasoning,
|
RequiresReasoning: requiresReasoning,
|
||||||
|
IsSoftwareDevelopment: isSoftwareDevelopment(userMessage),
|
||||||
}
|
}
|
||||||
|
log.Printf("[DEBUG] RouteContext built: msg=%q, IsSoftwareDevelopment=%v", userMessage, routeCtx.IsSoftwareDevelopment)
|
||||||
routeCtx.Tags = s.getRouteCtxTags(routeCtx)
|
routeCtx.Tags = s.getRouteCtxTags(routeCtx)
|
||||||
return routeCtx
|
return routeCtx
|
||||||
}
|
}
|
||||||
@@ -1034,12 +1039,14 @@ func (s *Server) buildRouteContextFromResponses(req models.ResponsesRequest) *ro
|
|||||||
strings.Contains(msgLower, "explain in detail")
|
strings.Contains(msgLower, "explain in detail")
|
||||||
|
|
||||||
routeCtx := &router.RouteContext{
|
routeCtx := &router.RouteContext{
|
||||||
UserMessage: userMessage,
|
UserMessage: userMessage,
|
||||||
InputTokens: inputTokens,
|
InputTokens: inputTokens,
|
||||||
HasMultimodalInput: hasMultimodal,
|
HasMultimodalInput: hasMultimodal,
|
||||||
RequiresToolCalling: requiresToolCalling,
|
RequiresToolCalling: requiresToolCalling,
|
||||||
RequiresReasoning: requiresReasoning,
|
RequiresReasoning: requiresReasoning,
|
||||||
|
IsSoftwareDevelopment: isSoftwareDevelopment(userMessage),
|
||||||
}
|
}
|
||||||
|
log.Printf("[DEBUG] RouteContext built: msg=%q, IsSoftwareDevelopment=%v", userMessage, routeCtx.IsSoftwareDevelopment)
|
||||||
routeCtx.Tags = s.getRouteCtxTags(routeCtx)
|
routeCtx.Tags = s.getRouteCtxTags(routeCtx)
|
||||||
return routeCtx
|
return routeCtx
|
||||||
}
|
}
|
||||||
@@ -1102,3 +1109,35 @@ func (s *Server) getRouteCtxTags(routeCtx *router.RouteContext) []string {
|
|||||||
|
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modelStripRegex = regexp.MustCompile(`(?i)\b[a-z0-9/._:-]*(code|coder|codex)[a-z0-9/._:-]*\b`)
|
||||||
|
memoryContextRegex = regexp.MustCompile(`(?s)<memory-context>.*?</memory-context>`)
|
||||||
|
softwareDevRegex = regexp.MustCompile(`(?i)\b(code|coding|programmer|programming|software|refactor|refactoring|compile|compiling|compiler|git|github|debug|debugging|repository|repo|unittest|unit test|test suite|test case|test cases|pull request|merge conflict|syntax error|source code|python|golang|javascript|typescript|ruby|php|perl|swift|kotlin|scala|rustlang|bash|powershell|sql|script|snippet|api|endpoint|patch|diff)\b`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func isSoftwareDevelopment(userMessage string) bool {
|
||||||
|
msgLower := strings.ToLower(userMessage)
|
||||||
|
sanitized := memoryContextRegex.ReplaceAllString(msgLower, "")
|
||||||
|
sanitized = modelStripRegex.ReplaceAllString(sanitized, "")
|
||||||
|
|
||||||
|
if strings.Contains(sanitized, "script") {
|
||||||
|
nonCodingScriptTerms := []string{
|
||||||
|
"speaker script", "dialogue script", "movie script", "theatre script",
|
||||||
|
"theater script", "script writing", "script writer", "video script",
|
||||||
|
"audio script", "podcast script", "script for Kayla", "script for the",
|
||||||
|
}
|
||||||
|
hasNonCodingScript := false
|
||||||
|
for _, term := range nonCodingScriptTerms {
|
||||||
|
if strings.Contains(sanitized, strings.ToLower(term)) {
|
||||||
|
hasNonCodingScript = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasNonCodingScript {
|
||||||
|
sanitized = strings.ReplaceAll(sanitized, "script", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return softwareDevRegex.MatchString(sanitized)
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ func (s *Server) handleGetSettings(c *gin.Context) {
|
|||||||
providerCount := 0
|
providerCount := 0
|
||||||
modelCount := 0
|
modelCount := 0
|
||||||
s.registryMu.RLock()
|
s.registryMu.RLock()
|
||||||
|
defer s.registryMu.RUnlock()
|
||||||
if s.registry != nil {
|
if s.registry != nil {
|
||||||
providerCount = len(s.registry.Providers)
|
providerCount = len(s.registry.Providers)
|
||||||
for _, p := range s.registry.Providers {
|
for _, p := range s.registry.Providers {
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ class MonitoringPage {
|
|||||||
simulateRequest() {
|
simulateRequest() {
|
||||||
const clients = ['client-1', 'client-2', 'client-3', 'client-4', 'client-5'];
|
const clients = ['client-1', 'client-2', 'client-3', 'client-4', 'client-5'];
|
||||||
const providers = ['OpenAI', 'Gemini', 'DeepSeek', 'Grok'];
|
const providers = ['OpenAI', 'Gemini', 'DeepSeek', 'Grok'];
|
||||||
const models = ['gpt-4o', 'gpt-4o-mini', 'gemini-2.0-flash', 'deepseek-chat', 'grok-4-1-fast-non-reasoning'];
|
const models = ['gpt-4o', 'gpt-4o-mini', 'gemini-2.5-flash', 'deepseek-chat', 'grok-4-1-fast-non-reasoning'];
|
||||||
const statuses = ['success', 'success', 'success', 'error', 'warning']; // Mostly success
|
const statuses = ['success', 'success', 'success', 'error', 'warning']; // Mostly success
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
|
|||||||
Reference in New Issue
Block a user