diff --git a/README_zh-CN.md b/README_zh-CN.md index 9f1250ea..10ed879b 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -95,7 +95,7 @@ _🚀 **Next Generation AI One-Stop Solution**_ - [x] Chat Completions (support *vision*, *tools_calling* and *function_calling*) - [x] Image Generation - [x] Azure OpenAI -- [x] Anthropic Claude (claude-2, claude-2.1, claude-instant) +- [x] Anthropic Claude (support *vision*) - [x] Slack Claude (deprecated) - [x] Sparkdesk (support *function_calling*) - [x] Google Gemini (PaLM2) diff --git a/app/src/admin/api/system.ts b/app/src/admin/api/system.ts index 0a428f65..39a387fd 100644 --- a/app/src/admin/api/system.ts +++ b/app/src/admin/api/system.ts @@ -15,6 +15,7 @@ export type GeneralState = { docs: string; file: string; pwa_manifest: string; + debug_mode: boolean; }; export type MailState = { @@ -122,6 +123,7 @@ export const initialSystemState: SystemProps = { docs: "", file: "", pwa_manifest: "", + debug_mode: false, }, site: { close_register: false, diff --git a/app/src/resources/i18n/cn.json b/app/src/resources/i18n/cn.json index 180e45e2..2a94870e 100644 --- a/app/src/resources/i18n/cn.json +++ b/app/src/resources/i18n/cn.json @@ -692,6 +692,8 @@ "backend": "后端域名", "backendTip": "后端域名(docker 安装默认路径为 /api),用于接收回调和存储等,默认为空", "backendPlaceholder": "后端回调域名,默认为空,接受回调必填(如 {{backend}})", + "debugMode": "调试模式", + "debugModeTip": "调试模式,开启后日志将输出详细的请求参数等的日志,用于排查问题", "mailHost": "发件域名", "mailPort": "SMTP 端口", "mailUser": "用户名", diff --git a/app/src/resources/i18n/en.json b/app/src/resources/i18n/en.json index 5af4a8e5..c5b0fd7a 100644 --- a/app/src/resources/i18n/en.json +++ b/app/src/resources/i18n/en.json @@ -570,7 +570,9 @@ "image_storeTip": "Images generated by the OpenAI channel DALL-E will be stored on the server to prevent invalidation of the images", "image_storeNoBackend": "No backend domain configured, cannot enable image storage", "closeRelay": "Turn off Staging API", - "closeRelayTip": "Turn off the staging API, the staging API will not be available after turning off" + "closeRelayTip": "Turn off the staging API, the staging API will not be available after turning off", + "debugMode": "debugging mode", + "debugModeTip": "Debug mode, after turning on, the log will output detailed request parameters and other logs for troubleshooting" }, "user": "Users", "invitation-code": "Invitation Code", diff --git a/app/src/resources/i18n/ja.json b/app/src/resources/i18n/ja.json index 8c240dd6..58c006fe 100644 --- a/app/src/resources/i18n/ja.json +++ b/app/src/resources/i18n/ja.json @@ -570,7 +570,9 @@ "image_storeTip": "OpenAIチャンネルDALL - Eによって生成された画像は、画像の無効化を防ぐためにサーバーに保存されます", "image_storeNoBackend": "バックエンドドメインが設定されていません。画像ストレージを有効にできません", "closeRelay": "ステージングAPIをオフにする", - "closeRelayTip": "ステージングAPIをオフにすると、オフにするとステージングAPIは使用できなくなります" + "closeRelayTip": "ステージングAPIをオフにすると、オフにするとステージングAPIは使用できなくなります", + "debugMode": "試験調整モード", + "debugModeTip": "デバッグモード、オンにすると、ログは詳細な要求パラメータとトラブルシューティングのための他のログを出力します" }, "user": "ユーザー管理", "invitation-code": "招待コード", diff --git a/app/src/resources/i18n/ru.json b/app/src/resources/i18n/ru.json index c804d4b1..d06a273c 100644 --- a/app/src/resources/i18n/ru.json +++ b/app/src/resources/i18n/ru.json @@ -570,7 +570,9 @@ "image_storeTip": "Изображения, сгенерированные каналом OpenAI DALL-E, будут храниться на сервере, чтобы предотвратить недействительность изображений", "image_storeNoBackend": "Нет настроенного внутреннего домена, невозможно включить хранение изображений", "closeRelay": "Отключить Staging API", - "closeRelayTip": "Отключите промежуточный API, промежуточный API будет недоступен после отключения" + "closeRelayTip": "Отключите промежуточный API, промежуточный API будет недоступен после отключения", + "debugMode": "Режим отладки", + "debugModeTip": "Режим отладки, после включения журнал выведет подробные параметры запроса и другие журналы для устранения неполадок" }, "user": "Управление пользователями", "invitation-code": "Код приглашения", diff --git a/app/src/routes/admin/System.tsx b/app/src/routes/admin/System.tsx index c16e58dc..539d10a0 100644 --- a/app/src/routes/admin/System.tsx +++ b/app/src/routes/admin/System.tsx @@ -236,6 +236,22 @@ function General({ data, dispatch, onChange }: CompProps) { + + + { + dispatch({ type: "update:general.debug_mode", value }); + }} + /> + +
diff --git a/channel/system.go b/channel/system.go index 04551b05..2f7285cc 100644 --- a/channel/system.go +++ b/channel/system.go @@ -31,6 +31,7 @@ type generalState struct { File string `json:"file" mapstructure:"file"` Docs string `json:"docs" mapstructure:"docs"` PWAManifest string `json:"pwa_manifest" mapstructure:"pwamanifest"` + DebugMode bool `json:"debug_mode" mapstructure:"debugmode"` } type siteState struct { @@ -94,6 +95,7 @@ func NewSystemConfig() *SystemConfig { func (c *SystemConfig) Load() { globals.NotifyUrl = c.GetBackend() + globals.DebugMode = c.General.DebugMode globals.CloseRegistration = c.Site.CloseRegister globals.CloseRelay = c.Site.CloseRelay diff --git a/globals/variables.go b/globals/variables.go index c0a4722f..3f45a185 100644 --- a/globals/variables.go +++ b/globals/variables.go @@ -11,6 +11,7 @@ const AnonymousMaxThread = 1 var AllowedOrigins []string +var DebugMode bool var NotifyUrl = "" var ArticlePermissionGroup []string var GenerationPermissionGroup []string diff --git a/utils/net.go b/utils/net.go index 0ee24513..50173771 100644 --- a/utils/net.go +++ b/utils/net.go @@ -74,8 +74,16 @@ func fillHeaders(req *http.Request, headers map[string]string) { } func Http(uri string, method string, ptr interface{}, headers map[string]string, body io.Reader, config []globals.ProxyConfig) (err error) { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http] %s %s\nheaders: \n%s\nbody: \n%s", method, uri, Marshal(headers), Marshal(body))) + } + req, err := http.NewRequest(method, uri, body) if err != nil { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http] failed to create request: %s", err)) + } + return err } fillHeaders(req, headers) @@ -83,20 +91,40 @@ func Http(uri string, method string, ptr interface{}, headers map[string]string, client := newClient(config) resp, err := client.Do(req) if err != nil { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http] failed to send request: %s", err)) + } + return err } defer resp.Body.Close() if err = json.NewDecoder(resp.Body).Decode(ptr); err != nil { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http] failed to decode response: %s\nresponse: %s", err, resp.Body)) + } + return err } + + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http] response: %s", Marshal(ptr))) + } return nil } func HttpRaw(uri string, method string, headers map[string]string, body io.Reader, config []globals.ProxyConfig) (data []byte, err error) { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http] %s %s\nheaders: \n%s\nbody: \n%s", method, uri, Marshal(headers), Marshal(body))) + } + req, err := http.NewRequest(method, uri, body) if err != nil { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http] failed to create request: %s", err)) + } + return nil, err } fillHeaders(req, headers) @@ -104,14 +132,26 @@ func HttpRaw(uri string, method string, headers map[string]string, body io.Reade client := newClient(config) resp, err := client.Do(req) if err != nil { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http] failed to send request: %s", err)) + } + return nil, err } defer resp.Body.Close() if data, err = io.ReadAll(resp.Body); err != nil { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http] failed to read response: %s", err)) + } + return nil, err } + + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http] response: %s", string(data))) + } return data, nil } @@ -159,9 +199,17 @@ func EventSource(method string, uri string, headers map[string]string, body inte http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http-stream] %s %s\nheaders: \n%s\nbody: \n%s", method, uri, Marshal(headers), Marshal(body))) + } + client := newClient(config) req, err := http.NewRequest(method, uri, ConvertBody(body)) if err != nil { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http-stream] failed to create request: %s", err)) + } + return err } @@ -169,12 +217,20 @@ func EventSource(method string, uri string, headers map[string]string, body inte res, err := client.Do(req) if err != nil { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http-stream] failed to send request: %s", err)) + } + return err } defer res.Body.Close() if res.StatusCode >= 400 { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http-stream] request failed with status: %s\nresponse: %s", res.Status, res.Body)) + } + if content, err := io.ReadAll(res.Body); err == nil { if form, err := Unmarshal[map[string]interface{}](content); err == nil { data := MarshalWithIndent(form, 2) @@ -197,6 +253,10 @@ func EventSource(method string, uri string, headers map[string]string, body inte data := string(buf[:n]) for _, item := range strings.Split(data, "\n") { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[http-stream] response: %s", item)) + } + segment := strings.TrimSpace(item) if len(segment) > 0 { if err := callback(segment); err != nil { diff --git a/utils/scanner.go b/utils/scanner.go index b80ec8d5..c3d217a0 100644 --- a/utils/scanner.go +++ b/utils/scanner.go @@ -45,9 +45,17 @@ func EventScanner(props *EventScannerProps, config ...globals.ProxyConfig) *Even } }() + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[sse] event source: %s %s\nheaders: %v\nbody: %v", props.Method, props.Uri, Marshal(props.Headers), Marshal(props.Body))) + } + client := newClient(config) req, err := http.NewRequest(props.Method, props.Uri, ConvertBody(props.Body)) if err != nil { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[sse] failed to create request: %s", err)) + } + return &EventScannerError{Error: err} } @@ -55,6 +63,10 @@ func EventScanner(props *EventScannerProps, config ...globals.ProxyConfig) *Even resp, err := client.Do(req) if err != nil { + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[sse] failed to send request: %s", err)) + } + return &EventScannerError{Error: err} } @@ -62,9 +74,14 @@ func EventScanner(props *EventScannerProps, config ...globals.ProxyConfig) *Even if resp.StatusCode >= 400 { // for error response + body := getErrorBody(resp) + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[sse] request failed with status: %s\nresponse: %s", resp.Status, body)) + } + return &EventScannerError{ Error: fmt.Errorf("request failed with status code: %d", resp.StatusCode), - Body: getErrorBody(resp), + Body: body, } } @@ -91,11 +108,16 @@ func EventScanner(props *EventScannerProps, config ...globals.ProxyConfig) *Even for scanner.Scan() { raw := scanner.Text() + if len(raw) <= 5 || !strings.HasPrefix(raw, "data:") { // for only `data:` partial raw or unexpected chunk continue } + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[sse] chunk: %s", raw)) + } + chunk := strings.TrimSpace(strings.TrimPrefix(raw, "data:")) if chunk == "[DONE]" || strings.HasPrefix(chunk, "[DONE]") { // for done signal diff --git a/utils/tokenizer.go b/utils/tokenizer.go index 728b1521..de38c010 100644 --- a/utils/tokenizer.go +++ b/utils/tokenizer.go @@ -2,6 +2,7 @@ package utils import ( "chat/globals" + "fmt" "github.com/pkoukk/tiktoken-go" "strings" ) @@ -55,6 +56,9 @@ func NumTokensFromMessages(messages []globals.Message, model string) (tokens int // return len([]rune(data)) * weight // use the recall method instead (default encoder model is gpt-3.5-turbo-0613) + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[tiktoken] error encoding messages: %s (model: %s), using default model instead", err, model)) + } return NumTokensFromMessages(messages, globals.GPT3Turbo0613) } @@ -65,6 +69,10 @@ func NumTokensFromMessages(messages []globals.Message, model string) (tokens int tokensPerMessage } tokens += 3 // every reply is primed with <|start|>assistant<|message|> + + if globals.DebugMode { + globals.Debug(fmt.Sprintf("[tiktoken] num tokens from messages: %d (tokens per message: %d, model: %s)", tokens, tokensPerMessage, model)) + } return tokens }