Files
hunli/hunliji-api/spark/client.go
2026-08-01 16:55:39 +08:00

113 lines
2.5 KiB
Go

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]) + "…"
}