From c49b4b63e7026817167b3235255ca2ff70f7d19b Mon Sep 17 00:00:00 2001 From: coolxll Date: Sat, 4 Apr 2026 13:39:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(httpapi):=20=E6=B7=BB=E5=8A=A0=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=90=8D=E7=A7=B0=E5=AD=97=E6=AE=B5=E5=B9=B6=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=9F=A5=E8=AF=A2=E6=A8=A1=E5=9E=8B=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 modelResponse 结构体中添加 Name 字段用于返回模型名称 - 实现 handleModels 接口返回模型的名称信息 - 创建 query-models 命令行工具用于查询和展示模型列表 - 实现 IPC 连接和模型数据提取功能 - 添加模型 ID 和场景键识别逻辑 - 支持多种模型字段映射和遍历解析 --- cmd/query-models/main.go | 151 +++++++++++++++++++++++++++++++++++++ internal/httpapi/server.go | 2 + 2 files changed, 153 insertions(+) create mode 100644 cmd/query-models/main.go diff --git a/cmd/query-models/main.go b/cmd/query-models/main.go new file mode 100644 index 0000000..b2035c4 --- /dev/null +++ b/cmd/query-models/main.go @@ -0,0 +1,151 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "time" + + "lingma-ipc-proxy/internal/lingmaipc" +) + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // 使用自动发现的传输方式 + opts, err := lingmaipc.ResolveDialOptions(lingmaipc.TransportAuto, "", "") + if err != nil { + log.Fatalf("Failed to resolve dial options: %v", err) + } + + fmt.Printf("Connecting to Lingma IPC...\n") + fmt.Printf("Transport: %s\n", opts.Transport) + fmt.Printf("PipePath: %s\n", opts.PipePath) + fmt.Printf("WebSocketURL: %s\n", opts.WebSocketURL) + fmt.Println() + + client, err := lingmaipc.Connect(ctx, opts) + if err != nil { + log.Fatalf("Failed to connect: %v", err) + } + defer client.Close() + + // 初始化 + if err := client.Request(ctx, "initialize", map[string]any{ + "protocolVersion": 1, + "clientCapabilities": map[string]any{}, + "timestamp": time.Now().UnixMilli(), + }, nil); err != nil { + log.Fatalf("Failed to initialize: %v", err) + } + fmt.Println("Initialized successfully") + fmt.Println() + + // 查询模型 + var raw any + if err := client.Request(ctx, "config/queryModels", map[string]any{}, &raw); err != nil { + log.Fatalf("Failed to query models: %v", err) + } + + // 打印原始结果 + fmt.Println("=== Raw IPC Response ===") + rawJSON, _ := json.MarshalIndent(raw, "", " ") + fmt.Println(string(rawJSON)) + fmt.Println() + + // 提取并打印模型列表 + fmt.Println("=== Extracted Models ===") + models := extractModels(raw) + for _, m := range models { + fmt.Printf("ID: %s, Name: %s, Scene: %s\n", m.ID, m.Name, m.Scene) + } +} + +type Model struct { + ID string + Name string + Scene string +} + +func extractModels(raw any) []Model { + seen := make(map[string]Model) + var walk func(scene string, value any) + walk = func(scene string, value any) { + switch typed := value.(type) { + case map[string]any: + id := firstString(typed, "id", "modelId", "key") + name := firstString(typed, "name", "label", "displayName", "title") + currentScene := scene + if currentScene == "" { + currentScene = firstString(typed, "scene", "sceneId", "category") + } + if id != "" && (name != "" || likelyModelID(id)) { + if name == "" { + name = id + } + seen[id] = Model{ID: id, Name: name, Scene: currentScene} + } + for key, child := range typed { + nextScene := currentScene + if nextScene == "" || isSceneKey(key) { + nextScene = key + } + walk(nextScene, child) + } + case []any: + for _, item := range typed { + walk(scene, item) + } + } + } + walk("", raw) + + models := make([]Model, 0, len(seen)) + for _, model := range seen { + models = append(models, model) + } + return models +} + +func likelyModelID(id string) bool { + lowered := id + return contains(lowered, "qwen") || contains(lowered, "model") || contains(lowered, "auto") || contains(lowered, "coder") +} + +func isSceneKey(key string) bool { + switch key { + case "assistant", "chat", "developer", "inline", "quest": + return true + default: + return false + } +} + +func firstString(m map[string]any, keys ...string) string { + for _, key := range keys { + if value, ok := m[key]; ok { + switch typed := value.(type) { + case string: + if typed != "" { + return typed + } + } + } + } + return "" +} + +func contains(s, substr string) bool { + return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsHelper(s, substr)) +} + +func containsHelper(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} diff --git a/internal/httpapi/server.go b/internal/httpapi/server.go index e1010ed..7c7a0b2 100644 --- a/internal/httpapi/server.go +++ b/internal/httpapi/server.go @@ -43,6 +43,7 @@ type modelResponse struct { Object string `json:"object"` Created int64 `json:"created"` OwnedBy string `json:"owned_by"` + Name string `json:"name,omitempty"` } func NewServer(addr string, svc *service.Service) *Server { @@ -122,6 +123,7 @@ func (s *Server) handleModels(w http.ResponseWriter, r *http.Request) { Object: "model", Created: created, OwnedBy: "lingma", + Name: model.Name, }) } writeJSON(w, http.StatusOK, map[string]any{