package spark import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "strings" "time" ) type Message struct { Role string `json:"role"` Content string `json:"content"` } type chatRequest struct { Model string `json:"model"` Messages []Message `json:"messages"` Stream bool `json:"stream"` Temperature float64 `json:"temperature,omitempty"` MaxTokens int `json:"max_tokens,omitempty"` } type chatResponse struct { Code int `json:"code"` Message string `json:"message"` Choices []struct { Message Message `json:"message"` } `json:"choices"` } type Client struct { APIURL string Password string Model string HTTP *http.Client } func NewFromEnv() *Client { return &Client{ APIURL: strings.TrimSpace(envOr("SPARK_API_URL", "https://spark-api-open.xf-yun.com/v1/chat/completions")), Password: strings.TrimSpace(os.Getenv("SPARK_API_PASSWORD")), Model: strings.TrimSpace(envOr("SPARK_MODEL", "lite")), HTTP: &http.Client{Timeout: 45 * time.Second}, } } func (c *Client) Enabled() bool { return c != nil && c.Password != "" } func (c *Client) Chat(messages []Message) (string, error) { if !c.Enabled() { return "", fmt.Errorf("AI 未配置") } body, _ := json.Marshal(chatRequest{ Model: c.Model, Messages: messages, Stream: false, Temperature: 0.8, MaxTokens: 512, }) req, err := http.NewRequest(http.MethodPost, c.APIURL, bytes.NewReader(body)) if err != nil { return "", err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+c.Password) res, err := c.HTTP.Do(req) if err != nil { return "", err } defer res.Body.Close() raw, _ := io.ReadAll(res.Body) if res.StatusCode < 200 || res.StatusCode >= 300 { return "", fmt.Errorf("星火接口 HTTP %d: %s", res.StatusCode, truncate(string(raw), 200)) } var parsed chatResponse if err := json.Unmarshal(raw, &parsed); err != nil { return "", fmt.Errorf("解析星火响应失败: %w", err) } if parsed.Code != 0 { return "", fmt.Errorf("星火返回错误 %d: %s", parsed.Code, parsed.Message) } if len(parsed.Choices) == 0 { return "", fmt.Errorf("星火未返回内容") } text := strings.TrimSpace(parsed.Choices[0].Message.Content) if text == "" { return "", fmt.Errorf("星火返回空内容") } return text, nil } func envOr(k, def string) string { if v := os.Getenv(k); v != "" { return v } return def } func truncate(s string, n int) string { r := []rune(s) if len(r) <= n { return s } return string(r[:n]) + "…" }