watchvuln
watchvuln copied to clipboard
AI 驱动的漏洞情报 SCA 分析
刚好集成和sca库对接,其中核心需要ai或者正则提取组件和判断是否推送,下面是核心代码提取代码
package util
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strconv"
"strings"
"time"
)
// AIExtractResult AI提取结果结构
type AIExtractResult struct {
Dependency string `json:"dependency"`
Version string `json:"version"`
Confidence float64 `json:"confidence,omitempty"`
RawResponse string `json:"raw_response,omitempty"`
}
// AIConfig AI配置
type AIConfig struct {
URL string `json:"url"`
APIKey string `json:"api_key"`
Model string `json:"model"`
FallbackModels []string `json:"fallback_models"`
Temperature float64 `json:"temperature"`
MaxTokens int `json:"max_tokens"`
Timeout time.Duration `json:"timeout"`
MaxRetries int `json:"max_retries"`
}
// DefaultAIConfig 默认AI配置
func DefaultAIConfig() *AIConfig {
return &AIConfig{
URL: "https://api.openai.com/v1/chat/completions",
Model: "gpt-4",
Temperature: 0.1,
MaxTokens: 5000,
Timeout: 30 * time.Second,
MaxRetries: 3,
}
}
// ExtractDependencyAndVersionWithRetry 带重试的AI提取
func ExtractDependencyAndVersionWithRetry(title, description string, config *AIConfig) (*AIExtractResult, error) {
if config == nil {
config = DefaultAIConfig()
}
// 尝试主模型和备用模型
models := append([]string{config.Model}, config.FallbackModels...)
var lastErr error
for _, model := range models {
cfgCopy := *config
cfgCopy.Model = model
for attempt := 0; attempt < cfgCopy.MaxRetries; attempt++ {
fmt.Printf("[AI提取] 尝试模型: %s, 第%d次尝试\n", model, attempt+1)
result, err := ExtractDependencyAndVersionAI(title, description, &cfgCopy)
if err == nil {
fmt.Printf("[AI提取] 模型: %s, 返回: %+v\n", model, result)
if isValidResult(result) {
fmt.Printf("[AI提取] 模型: %s, 提取成功: 依赖=%s, 版本=%s, 置信度=%.2f\n", model, result.Dependency, result.Version, result.Confidence)
return result, nil
}
lastErr = fmt.Errorf("AI返回结果无效: dependency=%s, version=%s", result.Dependency, result.Version)
fmt.Printf("[AI提取] 模型: %s, 结果无效: %v\n", model, lastErr)
} else {
lastErr = err
fmt.Printf("[AI提取] 模型: %s, 错误: %v\n", model, err)
}
if attempt < cfgCopy.MaxRetries-1 {
time.Sleep(time.Duration(attempt+1) * time.Second)
}
}
}
fmt.Printf("[AI提取] 所有模型均失败,最后错误: %v\n", lastErr)
return nil, fmt.Errorf("AI提取失败,主模型及所有备用模型均失败: %v", lastErr)
}
// ExtractComponentAndVersionEnhanced 增强版提取函数
func ExtractComponentAndVersionEnhanced(
title, description string,
aiEnabled, forceRegex bool,
config *AIConfig,
) (component, version string, confidence float64, err error) {
if aiEnabled && !forceRegex {
result, err := ExtractDependencyAndVersionWithRetry(title, description, config)
if err == nil && isValidResult(result) {
return result.Dependency, result.Version, result.Confidence, nil
}
fmt.Printf("AI提取失败,降级到正则提取: %v\n", err)
}
comp, ver, regexErr := ExtractDependencyAndVersionRegex(title, description)
if regexErr == nil {
return comp, ver, 0.6, nil
}
return "", "", 0, fmt.Errorf("AI和正则提取均失败: AI错误=%v, 正则错误=%v", err, regexErr)
}
// ExtractDependencyAndVersionAI AI方式提取(改进版)
func ExtractDependencyAndVersionAI(title, description string, config *AIConfig) (*AIExtractResult, error) {
if config == nil {
config = DefaultAIConfig()
}
prompt := buildRobustPrompt(title, description)
ctx, cancel := context.WithTimeout(context.Background(), config.Timeout)
defer cancel()
reqBody := map[string]interface{}{
"model": config.Model,
"temperature": config.Temperature,
"max_tokens": config.MaxTokens,
"messages": []map[string]string{
{"role": "system", "content": "你是专业的网络安全分析师,专门从漏洞信息中提取准确的依赖组件名和版本号。请严格按照JSON格式返回结果。"},
{"role": "user", "content": prompt},
},
}
body, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("构建请求失败: %v", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", config.URL, bytes.NewBuffer(body))
if err != nil {
return nil, fmt.Errorf("创建请求失败: %v", err)
}
req.Header.Set("Authorization", "Bearer "+config.APIKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("请求失败: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
respBody, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("[AI提取] 模型: %s, API返回错误状态码 %d: %s\n", config.Model, resp.StatusCode, string(respBody))
return nil, fmt.Errorf("API返回错误状态码 %d: %s", resp.StatusCode, string(respBody))
}
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %v", err)
}
var result struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
} `json:"choices"`
Error struct {
Message string `json:"message"`
} `json:"error"`
}
if err := json.Unmarshal(respBody, &result); err != nil {
fmt.Printf("[AI提取] 模型: %s, 解析API响应失败: %v, 原始内容: %s\n", config.Model, err, string(respBody))
return nil, fmt.Errorf("解析API响应失败: %v", err)
}
if len(result.Choices) > 0 {
fmt.Printf("[AI提取] 模型: %s, 原始返回: %s\n", config.Model, result.Choices[0].Message.Content)
}
if result.Error.Message != "" {
fmt.Printf("[AI提取] 模型: %s, API返回错误: %s\n", config.Model, result.Error.Message)
return nil, fmt.Errorf("API返回错误: %s", result.Error.Message)
}
if len(result.Choices) == 0 {
fmt.Printf("[AI提取] 模型: %s, API返回空结果\n", config.Model)
return nil, fmt.Errorf("API返回空结果")
}
content := strings.TrimSpace(result.Choices[0].Message.Content)
if content == "" {
fmt.Printf("[AI提取] 模型: %s, AI返回空内容\n", config.Model)
return nil, fmt.Errorf("AI返回空内容")
}
aiResult, err := parseAIResponse(content)
if err != nil {
fmt.Printf("[AI提取] 模型: %s, 解析AI响应失败: %v, 原始内容: %s\n", config.Model, err, content)
return nil, fmt.Errorf("解析AI响应失败: %v, 原始内容: %s", err, content)
}
aiResult.RawResponse = content
fmt.Printf("[AI提取] 模型: %s, 解析后结果: 依赖=%s, 版本=%s, 置信度=%.2f\n", config.Model, aiResult.Dependency, aiResult.Version, aiResult.Confidence)
return aiResult, nil
}
// buildRobustPrompt 构建健壮的prompt
func buildRobustPrompt(title, description string) string {
return fmt.Sprintf(`请从以下漏洞信息中提取最准确的依赖组件名(dependency)和版本号(version)。
要求:
1. 返回严格的JSON格式:{"dependency":"组件名","version":"版本号","confidence":0.0-1.0}
2. dependency: 软件包/库/组件的准确名称,去除无关词汇
3. version: 具体版本号(如1.2.3),如果有多个版本取最相关的
4. confidence: 提取结果的置信度(0.0-1.0)
5. 如果无法确定某个字段,请设为空字符串""
6. 不要添加任何解释文字,只返回JSON
漏洞信息:
标题: %s
描述: %s
请返回JSON:`, title, description)
}
// parseAIResponse 解析AI响应的多种格式
func parseAIResponse(content string) (*AIExtractResult, error) {
content = strings.TrimSpace(content)
strategies := []func(string) (*AIExtractResult, error){
parseDirectJSON,
parseMarkdownJSON,
parseTextWithJSON,
parseRegexExtraction,
parseLLMHallucination,
}
var lastErr error
for i, strategy := range strategies {
result, err := strategy(content)
if err == nil && isValidResult(result) {
if result.Confidence == 0 {
result.Confidence = 1.0 - float64(i)*0.1
}
return result, nil
}
lastErr = err
}
return nil, fmt.Errorf("所有解析策略均失败,最后错误: %v", lastErr)
}
// parseDirectJSON 解析直接的JSON格式
func parseDirectJSON(content string) (*AIExtractResult, error) {
var result AIExtractResult
err := json.Unmarshal([]byte(content), &result)
return &result, err
}
// parseMarkdownJSON 解析Markdown包裹的JSON
func parseMarkdownJSON(content string) (*AIExtractResult, error) {
patterns := []string{
"```json\n(.+?)\n```",
"```\n(.+?)\n```",
"`(.+?)`",
}
for _, pattern := range patterns {
re := regexp.MustCompile(`(?s)` + pattern)
if matches := re.FindStringSubmatch(content); len(matches) > 1 {
var result AIExtractResult
if err := json.Unmarshal([]byte(strings.TrimSpace(matches[1])), &result); err == nil {
return &result, nil
}
}
}
return nil, fmt.Errorf("未找到有效的Markdown JSON")
}
// parseTextWithJSON 解析文本+JSON混合格式
func parseTextWithJSON(content string) (*AIExtractResult, error) {
re := regexp.MustCompile(`\{[^{}]*"dependency"[^{}]*\}`)
matches := re.FindAllString(content, -1)
for _, match := range matches {
var result AIExtractResult
if err := json.Unmarshal([]byte(match), &result); err == nil {
return &result, nil
}
}
return nil, fmt.Errorf("未找到有效的JSON对象")
}
// parseRegexExtraction 正则提取JSON字段
func parseRegexExtraction(content string) (*AIExtractResult, error) {
result := &AIExtractResult{}
depPatterns := []string{
`"dependency"\s*:\s*"([^"]+)"`,
`dependency\s*[:=]\s*"?([^",\s}]+)"?`,
`组件[名称]*\s*[::]\s*"?([^",\s}]+)"?`,
}
for _, pattern := range depPatterns {
re := regexp.MustCompile(pattern)
if matches := re.FindStringSubmatch(content); len(matches) > 1 {
result.Dependency = strings.TrimSpace(matches[1])
break
}
}
verPatterns := []string{
`"version"\s*:\s*"([^"]+)"`,
`version\s*[:=]\s*"?([^",\s}]+)"?`,
`版本[号]*\s*[::]\s*"?([^",\s}]+)"?`,
`([0-9]+\.[0-9]+(?:\.[0-9]+)?)`,
}
for _, pattern := range verPatterns {
re := regexp.MustCompile(pattern)
if matches := re.FindStringSubmatch(content); len(matches) > 1 {
result.Version = strings.TrimSpace(matches[1])
break
}
}
confPatterns := []string{
`"confidence"\s*:\s*([0-9.]+)`,
`confidence\s*[:=]\s*([0-9.]+)`,
}
for _, pattern := range confPatterns {
re := regexp.MustCompile(pattern)
if matches := re.FindStringSubmatch(content); len(matches) > 1 {
if conf, err := strconv.ParseFloat(matches[1], 64); err == nil {
result.Confidence = conf
}
break
}
}
if result.Dependency != "" || result.Version != "" {
return result, nil
}
return nil, fmt.Errorf("正则提取失败")
}
// parseLLMHallucination 处理LLM幻觉内容
func parseLLMHallucination(content string) (*AIExtractResult, error) {
content = strings.ReplaceAll(content, "根据提供的漏洞信息", "")
content = strings.ReplaceAll(content, "分析如下", "")
content = strings.ReplaceAll(content, "提取结果为", "")
lines := strings.Split(content, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "{") && strings.HasSuffix(line, "}") {
var result AIExtractResult
if err := json.Unmarshal([]byte(line), &result); err == nil {
return &result, nil
}
}
}
return nil, fmt.Errorf("无法处理幻觉内容")
}
// isValidResult 验证结果有效性
func isValidResult(result *AIExtractResult) bool {
if result == nil {
return false
}
dep := strings.TrimSpace(result.Dependency)
ver := strings.TrimSpace(result.Version)
if dep == "" && ver == "" {
return false
}
if dep != "" && (len(dep) < 2 || len(dep) > 100) {
return false
}
if ver != "" {
versionPattern := regexp.MustCompile(`^[0-9]+(\.[0-9]+)*([a-zA-Z0-9\-\+]*)?$`)
if !versionPattern.MatchString(ver) {
return false
}
}
return true
}
// ExtractDependencyAndVersionRegex 正则方式提取(增强版)
func ExtractDependencyAndVersionRegex(title, description string) (string, string, error) {
text := title + " " + description
patterns := []struct {
name string
pattern string
depIdx int
verIdx int
}{
{"标准格式", `([A-Za-z0-9\-_\.]+)[\s,,、]+([0-9]+\.[0-9]+(?:\.[0-9]+)?)`, 1, 2},
{"漏洞标题格式", `^([A-Za-z0-9\-_\.]+)[\s]*漏洞.*?([0-9]+\.[0-9]+(?:\.[0-9]+)?)`, 1, 2},
{"版本号在前", `([0-9]+\.[0-9]+(?:\.[0-9]+)?)[\s]*([A-Za-z0-9\-_\.]+)`, 2, 1},
{"中文描述", `([A-Za-z0-9\-_\.]+)[\s]*组件.*?([0-9]+\.[0-9]+(?:\.[0-9]+)?)`, 1, 2},
{"CVE格式", `([A-Za-z0-9\-_\.]+)[\s]*([0-9]+\.[0-9]+(?:\.[0-9]+)?)[\s]*存在`, 1, 2},
{"英文描述", `([A-Za-z0-9\-_\.]+)[\s]+version[\s]*([0-9]+\.[0-9]+(?:\.[0-9]+)?)`, 1, 2},
{"括号版本", `([A-Za-z0-9\-_\.]+)[\s]*\(([0-9]+\.[0-9]+(?:\.[0-9]+)?)\)`, 1, 2},
{"冒号分隔", `([A-Za-z0-9\-_\.]+)[\s]*:[\s]*([0-9]+\.[0-9]+(?:\.[0-9]+)?)`, 1, 2},
}
// 尝试匹配所有模式
for _, p := range patterns {
re := regexp.MustCompile(p.pattern)
if matches := re.FindStringSubmatch(text); len(matches) > max(p.depIdx, p.verIdx) {
dependency := strings.TrimSpace(matches[p.depIdx])
version := strings.TrimSpace(matches[p.verIdx])
if dependency != "" && version != "" {
return dependency, version, nil
}
}
}
// 单独提取组件名的模式
componentPatterns := []string{
`^([A-Za-z0-9\-_\.]+)[\s]*漏洞`,
`([A-Za-z0-9\-_\.]+)[\s]*存在安全漏洞`,
`([A-Za-z0-9\-_\.]+)[\s]*组件`,
`^([A-Za-z][A-Za-z0-9\-_\.]{1,})`,
}
component := ""
for _, pattern := range componentPatterns {
re := regexp.MustCompile(pattern)
if matches := re.FindStringSubmatch(title); len(matches) > 1 {
component = strings.TrimSpace(matches[1])
break
}
}
// 单独提取版本号的模式
versionPatterns := []string{
`([0-9]+\.[0-9]+\.[0-9]+)`,
`([0-9]+\.[0-9]+)`,
`v([0-9]+\.[0-9]+(?:\.[0-9]+)?)`,
`版本[\s]*([0-9]+\.[0-9]+(?:\.[0-9]+)?)`,
`version[\s]*([0-9]+\.[0-9]+(?:\.[0-9]+)?)`,
}
version := ""
for _, pattern := range versionPatterns {
re := regexp.MustCompile(pattern)
if matches := re.FindStringSubmatch(text); len(matches) > 1 {
version = strings.TrimSpace(matches[1])
break
}
}
// 清理和验证结果
component = cleanComponentName(component)
version = cleanVersionString(version)
if component != "" || version != "" {
return component, version, nil
}
return "", "", fmt.Errorf("未匹配到组件和版本")
}
// max 返回两个数中的较大值
func max(a, b int) int {
if a > b {
return a
}
return b
}
// cleanComponentName 清理组件名
func cleanComponentName(component string) string {
if component == "" {
return ""
}
// 移除常见的无关词汇
removeWords := []string{"漏洞", "组件", "软件", "库", "包", "vulnerability", "component", "software", "library", "package"}
for _, word := range removeWords {
component = strings.ReplaceAll(component, word, "")
}
// 移除特殊字符
component = regexp.MustCompile(`[^\w\-\.]`).ReplaceAllString(component, "")
return strings.TrimSpace(component)
}
// cleanVersionString 清理版本号字符串
func cleanVersionString(version string) string {
if version == "" {
return ""
}
// 移除版本号前缀
version = strings.TrimPrefix(version, "v")
version = strings.TrimPrefix(version, "V")
// 确保版本号格式正确
re := regexp.MustCompile(`^([0-9]+(?:\.[0-9]+)*(?:[a-zA-Z0-9\-\+]*)?)`) // 这里括号要闭合
if matches := re.FindStringSubmatch(version); len(matches) > 1 {
return matches[1]
}
return strings.TrimSpace(version)
}
// ExtractDependencyAndVersionFallback 兜底提取函数
func ExtractDependencyAndVersionFallback(title, description string) (string, string, error) {
text := strings.ToLower(title + " " + description)
// 通用软件包名模式
commonPackages := []string{
"apache", "nginx", "mysql", "postgresql", "redis", "mongodb", "elasticsearch",
"spring", "struts", "hibernate", "jackson", "fastjson", "log4j", "slf4j",
"jquery", "react", "vue", "angular", "bootstrap", "lodash", "moment",
"openssl", "curl", "wget", "git", "svn", "docker", "kubernetes",
"python", "pip", "django", "flask", "requests", "numpy", "pandas",
"node", "npm", "express", "socket", "webpack", "babel", "typescript",
"java", "maven", "gradle", "junit", "mockito", "guava", "commons",
"php", "composer", "symfony", "laravel", "wordpress", "drupal",
"ruby", "gem", "rails", "sinatra", "bundler", "rake",
"go", "gin", "echo", "beego", "gorm", "cobra",
"dotnet", "nuget", "entityframework", "newtonsoft", "autofac",
}
for _, pkg := range commonPackages {
if strings.Contains(text, pkg) {
// 尝试提取版本号
versionRegex := regexp.MustCompile(pkg + `[\s\-_]*([0-9]+\.[0-9]+(?:\.[0-9]+)?)`)
if matches := versionRegex.FindStringSubmatch(text); len(matches) > 1 {
return pkg, matches[1], nil
}
return pkg, "", nil
}
}
return "", "", fmt.Errorf("兜底提取失败")
}
// ValidateExtractResult 验证提取结果
func ValidateExtractResult(dependency, version string) (bool, string) {
if dependency == "" && version == "" {
return false, "依赖名和版本号均为空"
}
if dependency != "" {
if len(dependency) < 2 {
return false, "依赖名过短"
}
if len(dependency) > 100 {
return false, "依赖名过长"
}
// 检查是否包含有效字符
if !regexp.MustCompile(`^[A-Za-z0-9\-_\.]+$`).MatchString(dependency) {
return false, "依赖名包含无效字符"
}
}
if version != "" {
// 版本号格式验证
versionPattern := regexp.MustCompile(`^[0-9]+(\.[0-9]+)*([a-zA-Z0-9\-\+]*)?$`)
if !versionPattern.MatchString(version) {
return false, "版本号格式无效"
}
}
return true, ""
}
// ExtractDependencyAndVersionSmart 智能提取函数(主入口)
func ExtractDependencyAndVersionSmart(
title, description string,
aiEnabled bool,
config *AIConfig,
) (dependency, version string, confidence float64, method string, err error) {
// 输入验证
if strings.TrimSpace(title) == "" && strings.TrimSpace(description) == "" {
return "", "", 0, "", fmt.Errorf("标题和描述均为空")
}
// 方法1: AI提取(如果启用)
if aiEnabled && config != nil && config.APIKey != "" {
if result, err := ExtractDependencyAndVersionWithRetry(title, description, config); err == nil {
if valid, _ := ValidateExtractResult(result.Dependency, result.Version); valid {
return result.Dependency, result.Version, result.Confidence, "AI", nil
}
}
}
// 方法2: 正则提取
if dep, ver, err := ExtractDependencyAndVersionRegex(title, description); err == nil {
if valid, _ := ValidateExtractResult(dep, ver); valid {
return dep, ver, 0.7, "正则", nil
}
}
// 方法3: 兜底提取
if dep, ver, err := ExtractDependencyAndVersionFallback(title, description); err == nil {
if valid, _ := ValidateExtractResult(dep, ver); valid {
return dep, ver, 0.5, "兜底", nil
}
}
return "", "", 0, "", fmt.Errorf("所有提取方法均失败")
}
// LogExtractResult 记录提取结果(用于调试和统计)
func LogExtractResult(title, description, dependency, version, method string, confidence float64, success bool) {
// 这里可以添加日志记录逻辑
fmt.Printf("[提取结果] 方法=%s, 成功=%v, 置信度=%.2f, 依赖=%s, 版本=%s\n",
method, success, confidence, dependency, version)
}
// BatchExtract 批量提取(用于批处理)
func BatchExtract(items []struct{ Title, Description string }, config *AIConfig) []AIExtractResult {
results := make([]AIExtractResult, len(items))
for i, item := range items {
dep, ver, conf, method, err := ExtractDependencyAndVersionSmart(
item.Title, item.Description, true, config)
results[i] = AIExtractResult{
Dependency: dep,
Version: ver,
Confidence: conf,
}
if err != nil {
results[i].RawResponse = fmt.Sprintf("错误: %v, 方法: %s", err, method)
} else {
results[i].RawResponse = fmt.Sprintf("方法: %s", method)
}
}
return results
}
// 兼容老接口
func ExtractComponentAndVersion(
title, description string,
aiEnabled, forceRegex bool,
aiUrl, aiKey, aiModel string,
) (component, version string, err error) {
cfg := &AIConfig{
URL: aiUrl,
APIKey: aiKey,
Model: aiModel,
Temperature: 0.1,
MaxTokens: 500,
Timeout: 30 * time.Second,
MaxRetries: 3,
}
// 如果forceRegex为true,则只用正则
if forceRegex {
return ExtractDependencyAndVersionRegex(title, description)
}
dep, ver, _, _, err := ExtractDependencyAndVersionSmart(title, description, aiEnabled, cfg)
return dep, ver, err
}
// AI辅助判断WSS依赖库是否与组件真正相关
func AIJudgeWSSMatch(component, version string, wssLibs []string, config *AIConfig) (string, error) {
prompt := fmt.Sprintf(`你是专业的软件成分分析专家。现在有如下信息:
组件名: %s
版本: %s
WSS依赖库列表如下(每行为一个依赖,格式为"依赖名 - 版本 (项目: 项目名)"):
%s
请判断哪些依赖库真正属于该组件,哪些是无关的,并简要说明理由,如果全部无关请直接回复"无真正相关依赖"。
请只输出:
1. 相关依赖(如有,列出)
逐个输出相关的:依赖名 - 版本 (项目: 项目名)
2. 你的关键分析结论(简明扼要)
不要输出JSON,不要重复组件名和版本号,只输出分析文本,总输出请控制在10000字符以内。`,
component, version, strings.Join(wssLibs, "\n"))
text, err := CallAITextOnly(prompt, config)
if err != nil {
return "", err
}
return text, nil
}
// CallAITextOnly 让AI返回纯文本分析
func CallAITextOnly(prompt string, config *AIConfig) (string, error) {
if config == nil {
config = DefaultAIConfig()
}
ctx, cancel := context.WithTimeout(context.Background(), config.Timeout)
defer cancel()
reqBody := map[string]interface{}{
"model": config.Model,
"temperature": config.Temperature,
"max_tokens": config.MaxTokens,
"messages": []map[string]string{
{"role": "system", "content": "你是专业的软件成分分析专家,只输出分析结论文本,不要输出JSON,不要重复组件名和版本号。"},
{"role": "user", "content": prompt},
},
}
body, err := json.Marshal(reqBody)
if err != nil {
return "", fmt.Errorf("构建请求失败: %v", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", config.URL, bytes.NewBuffer(body))
if err != nil {
return "", fmt.Errorf("创建请求失败: %v", err)
}
req.Header.Set("Authorization", "Bearer "+config.APIKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("请求失败: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
respBody, _ := ioutil.ReadAll(resp.Body)
return "", fmt.Errorf("API返回错误状态码 %d: %s", resp.StatusCode, string(respBody))
}
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("读取响应失败: %v", err)
}
var result struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
} `json:"choices"`
Error struct {
Message string `json:"message"`
} `json:"error"`
}
if err := json.Unmarshal(respBody, &result); err != nil {
return "", fmt.Errorf("解析API响应失败: %v", err)
}
if result.Error.Message != "" {
return "", fmt.Errorf("API返回错误: %s", result.Error.Message)
}
if len(result.Choices) == 0 {
return "", fmt.Errorf("API返回空结果")
}
content := strings.TrimSpace(result.Choices[0].Message.Content)
if len(content) > 10000 {
content = content[:10000]
}
return content, nil
}
我稍微调整了一下代码格式,方便查看。
这个 sca 分析写的挺细致的,考虑了很多种情况,能看出来大佬是打磨了很久的成果。不过想保持这个项目简单纯粹一些,暂时不打算融合到代码库里,先放在这给其他人参考一下吧
我稍微调整了一下代码格式,方便查看。
这个 sca 分析写的挺细致的,考虑了很多种情况,能看出来大佬是打磨了很久的成果。不过想保持这个项目简单纯粹一些,暂时不打算融合到代码库里,先放在这给其他人参考一下吧
那就做成sdk吧 铁铁