mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-03-08 06:43:41 +00:00
368 lines
9.5 KiB
Go
368 lines
9.5 KiB
Go
// 测试脚本 2:通过 CLIProxyAPIPlus 代理层排查问题
|
||
// 运行方式: go run test_proxy_debug.go
|
||
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
ProxyURL = "http://localhost:8317"
|
||
APIKey = "your-api-key-1"
|
||
)
|
||
|
||
func main() {
|
||
fmt.Println("=" + strings.Repeat("=", 59))
|
||
fmt.Println(" CLIProxyAPIPlus 代理层问题排查")
|
||
fmt.Println("=" + strings.Repeat("=", 59))
|
||
|
||
// Step 1: 检查代理服务状态
|
||
fmt.Println("\n[Step 1] 检查代理服务状态")
|
||
fmt.Println("-" + strings.Repeat("-", 59))
|
||
|
||
resp, err := http.Get(ProxyURL + "/health")
|
||
if err != nil {
|
||
fmt.Printf("❌ 代理服务不可达: %v\n", err)
|
||
fmt.Println("请确保服务正在运行: go run ./cmd/server/main.go")
|
||
return
|
||
}
|
||
resp.Body.Close()
|
||
fmt.Printf("✅ 代理服务正常 (HTTP %d)\n", resp.StatusCode)
|
||
|
||
// Step 2: 获取模型列表
|
||
fmt.Println("\n[Step 2] 获取模型列表")
|
||
fmt.Println("-" + strings.Repeat("-", 59))
|
||
|
||
models := getModels()
|
||
if len(models) == 0 {
|
||
fmt.Println("❌ 没有可用的模型,检查凭据加载")
|
||
checkCredentials()
|
||
return
|
||
}
|
||
fmt.Printf("✅ 找到 %d 个模型:\n", len(models))
|
||
for _, m := range models {
|
||
fmt.Printf(" - %s\n", m)
|
||
}
|
||
|
||
// Step 3: 测试模型请求 - 捕获详细错误
|
||
fmt.Println("\n[Step 3] 测试模型请求(详细日志)")
|
||
fmt.Println("-" + strings.Repeat("-", 59))
|
||
|
||
if len(models) > 0 {
|
||
testModel := models[0]
|
||
testModelRequest(testModel)
|
||
}
|
||
|
||
// Step 4: 检查代理内部 Token 状态
|
||
fmt.Println("\n[Step 4] 检查代理服务加载的凭据")
|
||
fmt.Println("-" + strings.Repeat("-", 59))
|
||
|
||
checkProxyCredentials()
|
||
|
||
// Step 5: 对比直接请求和代理请求
|
||
fmt.Println("\n[Step 5] 对比直接请求 vs 代理请求")
|
||
fmt.Println("-" + strings.Repeat("-", 59))
|
||
|
||
compareDirectVsProxy()
|
||
|
||
fmt.Println("\n" + strings.Repeat("=", 60))
|
||
fmt.Println(" 排查完成")
|
||
fmt.Println(strings.Repeat("=", 60))
|
||
}
|
||
|
||
func getModels() []string {
|
||
req, _ := http.NewRequest("GET", ProxyURL+"/v1/models", nil)
|
||
req.Header.Set("Authorization", "Bearer "+APIKey)
|
||
|
||
client := &http.Client{Timeout: 30 * time.Second}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
fmt.Printf("❌ 请求失败: %v\n", err)
|
||
return nil
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, _ := io.ReadAll(resp.Body)
|
||
|
||
if resp.StatusCode != 200 {
|
||
fmt.Printf("❌ HTTP %d: %s\n", resp.StatusCode, string(body))
|
||
return nil
|
||
}
|
||
|
||
var result struct {
|
||
Data []struct {
|
||
ID string `json:"id"`
|
||
} `json:"data"`
|
||
}
|
||
json.Unmarshal(body, &result)
|
||
|
||
models := make([]string, len(result.Data))
|
||
for i, m := range result.Data {
|
||
models[i] = m.ID
|
||
}
|
||
return models
|
||
}
|
||
|
||
func checkCredentials() {
|
||
homeDir, _ := os.UserHomeDir()
|
||
cliProxyDir := filepath.Join(homeDir, ".cli-proxy-api")
|
||
|
||
fmt.Printf("\n检查凭据目录: %s\n", cliProxyDir)
|
||
files, err := os.ReadDir(cliProxyDir)
|
||
if err != nil {
|
||
fmt.Printf("❌ 无法读取目录: %v\n", err)
|
||
return
|
||
}
|
||
|
||
for _, f := range files {
|
||
if strings.HasSuffix(f.Name(), ".json") {
|
||
fmt.Printf(" 📄 %s\n", f.Name())
|
||
}
|
||
}
|
||
}
|
||
|
||
func testModelRequest(model string) {
|
||
fmt.Printf("测试模型: %s\n", model)
|
||
|
||
payload := map[string]interface{}{
|
||
"model": model,
|
||
"messages": []map[string]string{
|
||
{"role": "user", "content": "Say 'OK' if you receive this."},
|
||
},
|
||
"max_tokens": 50,
|
||
"stream": false,
|
||
}
|
||
body, _ := json.Marshal(payload)
|
||
|
||
req, _ := http.NewRequest("POST", ProxyURL+"/v1/chat/completions", bytes.NewBuffer(body))
|
||
req.Header.Set("Authorization", "Bearer "+APIKey)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
fmt.Println("\n发送请求:")
|
||
fmt.Printf(" URL: %s/v1/chat/completions\n", ProxyURL)
|
||
fmt.Printf(" Model: %s\n", model)
|
||
|
||
client := &http.Client{Timeout: 60 * time.Second}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
fmt.Printf("❌ 请求失败: %v\n", err)
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
|
||
fmt.Printf("\n响应:\n")
|
||
fmt.Printf(" Status: %d\n", resp.StatusCode)
|
||
fmt.Printf(" Headers:\n")
|
||
for k, v := range resp.Header {
|
||
fmt.Printf(" %s: %s\n", k, strings.Join(v, ", "))
|
||
}
|
||
|
||
// 格式化 JSON 输出
|
||
var prettyJSON bytes.Buffer
|
||
if err := json.Indent(&prettyJSON, respBody, " ", " "); err == nil {
|
||
fmt.Printf(" Body:\n %s\n", prettyJSON.String())
|
||
} else {
|
||
fmt.Printf(" Body: %s\n", string(respBody))
|
||
}
|
||
|
||
if resp.StatusCode == 200 {
|
||
fmt.Println("\n✅ 请求成功!")
|
||
} else {
|
||
fmt.Println("\n❌ 请求失败!分析错误原因...")
|
||
analyzeError(respBody)
|
||
}
|
||
}
|
||
|
||
func analyzeError(body []byte) {
|
||
var errResp struct {
|
||
Message string `json:"message"`
|
||
Reason string `json:"reason"`
|
||
Error struct {
|
||
Message string `json:"message"`
|
||
Type string `json:"type"`
|
||
} `json:"error"`
|
||
}
|
||
json.Unmarshal(body, &errResp)
|
||
|
||
if errResp.Message != "" {
|
||
fmt.Printf("错误消息: %s\n", errResp.Message)
|
||
}
|
||
if errResp.Reason != "" {
|
||
fmt.Printf("错误原因: %s\n", errResp.Reason)
|
||
}
|
||
if errResp.Error.Message != "" {
|
||
fmt.Printf("错误详情: %s (类型: %s)\n", errResp.Error.Message, errResp.Error.Type)
|
||
}
|
||
|
||
// 分析常见错误
|
||
bodyStr := string(body)
|
||
if strings.Contains(bodyStr, "bearer token") || strings.Contains(bodyStr, "invalid") {
|
||
fmt.Println("\n可能的原因:")
|
||
fmt.Println(" 1. Token 已过期 - 需要刷新")
|
||
fmt.Println(" 2. Token 格式不正确 - 检查凭据文件")
|
||
fmt.Println(" 3. 代理服务加载了旧的 Token")
|
||
}
|
||
}
|
||
|
||
func checkProxyCredentials() {
|
||
// 尝试通过管理 API 获取凭据状态
|
||
req, _ := http.NewRequest("GET", ProxyURL+"/v0/management/auth/list", nil)
|
||
// 使用配置中的管理密钥 admin123
|
||
req.Header.Set("Authorization", "Bearer admin123")
|
||
|
||
client := &http.Client{Timeout: 10 * time.Second}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
fmt.Printf("❌ 无法访问管理 API: %v\n", err)
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, _ := io.ReadAll(resp.Body)
|
||
|
||
if resp.StatusCode == 200 {
|
||
fmt.Println("管理 API 返回的凭据列表:")
|
||
var prettyJSON bytes.Buffer
|
||
if err := json.Indent(&prettyJSON, body, " ", " "); err == nil {
|
||
fmt.Printf("%s\n", prettyJSON.String())
|
||
} else {
|
||
fmt.Printf("%s\n", string(body))
|
||
}
|
||
} else {
|
||
fmt.Printf("管理 API 返回: HTTP %d\n", resp.StatusCode)
|
||
fmt.Printf("响应: %s\n", truncate(string(body), 200))
|
||
}
|
||
}
|
||
|
||
func compareDirectVsProxy() {
|
||
homeDir, _ := os.UserHomeDir()
|
||
tokenPath := filepath.Join(homeDir, ".aws", "sso", "cache", "kiro-auth-token.json")
|
||
|
||
data, err := os.ReadFile(tokenPath)
|
||
if err != nil {
|
||
fmt.Printf("❌ 无法读取 Token 文件: %v\n", err)
|
||
return
|
||
}
|
||
|
||
var token struct {
|
||
AccessToken string `json:"accessToken"`
|
||
Region string `json:"region"`
|
||
}
|
||
json.Unmarshal(data, &token)
|
||
|
||
if token.Region == "" {
|
||
token.Region = "us-east-1"
|
||
}
|
||
|
||
// 直接请求
|
||
fmt.Println("\n1. 直接请求 Kiro API:")
|
||
directSuccess := testDirectKiroAPI(token.AccessToken, token.Region)
|
||
|
||
// 通过代理请求
|
||
fmt.Println("\n2. 通过代理请求:")
|
||
proxySuccess := testProxyAPI()
|
||
|
||
// 结论
|
||
fmt.Println("\n结论:")
|
||
if directSuccess && !proxySuccess {
|
||
fmt.Println(" ⚠️ 直接请求成功,代理请求失败")
|
||
fmt.Println(" 问题在于 CLIProxyAPIPlus 代理层")
|
||
fmt.Println(" 可能原因:")
|
||
fmt.Println(" 1. 代理服务使用了过期的 Token")
|
||
fmt.Println(" 2. Token 刷新逻辑有问题")
|
||
fmt.Println(" 3. 请求头构造不正确")
|
||
} else if directSuccess && proxySuccess {
|
||
fmt.Println(" ✅ 两者都成功")
|
||
} else if !directSuccess && !proxySuccess {
|
||
fmt.Println(" ❌ 两者都失败 - Token 本身可能有问题")
|
||
}
|
||
}
|
||
|
||
func testDirectKiroAPI(accessToken, region string) bool {
|
||
url := fmt.Sprintf("https://codewhisperer.%s.amazonaws.com", region)
|
||
|
||
payload := map[string]interface{}{
|
||
"origin": "AI_EDITOR",
|
||
"isEmailRequired": true,
|
||
"resourceType": "AGENTIC_REQUEST",
|
||
}
|
||
body, _ := json.Marshal(payload)
|
||
|
||
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body))
|
||
req.Header.Set("Content-Type", "application/x-amz-json-1.0")
|
||
req.Header.Set("x-amz-target", "AmazonCodeWhispererService.GetUsageLimits")
|
||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||
req.Header.Set("Accept", "application/json")
|
||
|
||
client := &http.Client{Timeout: 30 * time.Second}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
fmt.Printf(" ❌ 请求失败: %v\n", err)
|
||
return false
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode == 200 {
|
||
fmt.Printf(" ✅ 成功 (HTTP %d)\n", resp.StatusCode)
|
||
return true
|
||
}
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
fmt.Printf(" ❌ 失败 (HTTP %d): %s\n", resp.StatusCode, truncate(string(respBody), 100))
|
||
return false
|
||
}
|
||
|
||
func testProxyAPI() bool {
|
||
models := getModels()
|
||
if len(models) == 0 {
|
||
fmt.Println(" ❌ 没有可用模型")
|
||
return false
|
||
}
|
||
|
||
payload := map[string]interface{}{
|
||
"model": models[0],
|
||
"messages": []map[string]string{
|
||
{"role": "user", "content": "Say OK"},
|
||
},
|
||
"max_tokens": 10,
|
||
"stream": false,
|
||
}
|
||
body, _ := json.Marshal(payload)
|
||
|
||
req, _ := http.NewRequest("POST", ProxyURL+"/v1/chat/completions", bytes.NewBuffer(body))
|
||
req.Header.Set("Authorization", "Bearer "+APIKey)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
client := &http.Client{Timeout: 60 * time.Second}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
fmt.Printf(" ❌ 请求失败: %v\n", err)
|
||
return false
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode == 200 {
|
||
fmt.Printf(" ✅ 成功 (HTTP %d)\n", resp.StatusCode)
|
||
return true
|
||
}
|
||
respBody, _ := io.ReadAll(resp.Body)
|
||
fmt.Printf(" ❌ 失败 (HTTP %d): %s\n", resp.StatusCode, truncate(string(respBody), 100))
|
||
return false
|
||
}
|
||
|
||
func truncate(s string, n int) string {
|
||
if len(s) <= n {
|
||
return s
|
||
}
|
||
return s[:n] + "..."
|
||
}
|