Files
wasm-go/internal/api/llm.go
lychang 7f5350db62 update
2026-05-20 03:25:35 +08:00

322 lines
9.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api
import (
"encoding/json"
"fmt"
"strings"
)
// LLMClient 大语言模型客户端
type LLMClient struct {
httpClient *HTTPClient
config LLMConfig
}
// LLMConfig LLM配置
type LLMConfig struct {
APIKey string `json:"api_key"` // API密钥
BaseURL string `json:"base_url"` // API基础URL
Model string `json:"model"` // 模型名称
Provider string `json:"provider"` // 提供商: "openai", "ollama", "custom"
Temperature float64 `json:"temperature"` // 温度参数
MaxTokens int `json:"max_tokens"` // 最大token数
}
// ChatMessage 聊天消息
type ChatMessage struct {
Role string `json:"role"` // 角色: "system", "user", "assistant"
Content string `json:"content"` // 消息内容
}
// ChatRequest 聊天请求 - OpenAI格式
type ChatRequest struct {
Model string `json:"model"` // 模型名称
Messages []ChatMessage `json:"messages"` // 消息列表
Temperature float64 `json:"temperature,omitempty"` // 温度参数
MaxTokens int `json:"max_tokens,omitempty"` // 最大token数
TopP float64 `json:"top_p,omitempty"` // 采样参数
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"` // 频率惩罚
PresencePenalty float64 `json:"presence_penalty,omitempty"` // 存在惩罚
Stream bool `json:"stream,omitempty"` // 是否流式输出
}
// OllamaMessage Ollama消息格式
type OllamaMessage struct {
Role string `json:"role"` // 角色: "system", "user", "assistant"
Content string `json:"content"` // 消息内容
}
// OllamaRequest Ollama请求格式
type OllamaRequest struct {
Model string `json:"model"` // 模型名称
Messages []OllamaMessage `json:"messages"` // 消息列表
Stream bool `json:"stream"` // 是否流式输出
Options OllamaOptions `json:"options,omitempty"` // 选项
KeepAlive string `json:"keep_alive,omitempty"` // 保持连接时间
Think bool `json:"think"` // 是否思考模式
}
// OllamaOptions Ollama选项
type OllamaOptions struct {
Temperature float64 `json:"temperature,omitempty"` // 温度参数
MaxTokens int `json:"num_predict,omitempty"` // 最大预测token数
TopP float64 `json:"top_p,omitempty"` // 采样参数
}
// ChatResponse 聊天响应 - OpenAI格式
type ChatResponse struct {
ID string `json:"id"` // 响应ID
Object string `json:"object"` // 对象类型
Created int64 `json:"created"` // 创建时间
Model string `json:"model"` // 模型名称
Choices []Choice `json:"choices"` // 选择列表
Usage Usage `json:"usage"` // 使用情况
}
// OllamaResponse Ollama响应格式
type OllamaResponse struct {
Model string `json:"model"` // 模型名称
CreatedAt string `json:"created_at"` // 创建时间
Message OllamaMessage `json:"message"` // 消息
Done bool `json:"done"` // 是否完成
TotalDuration int64 `json:"total_duration,omitempty"` // 总耗时
LoadDuration int64 `json:"load_duration,omitempty"` // 加载耗时
PromptEvalCount int `json:"prompt_eval_count,omitempty"` // 提示评估计数
PromptEvalDuration int64 `json:"prompt_eval_duration,omitempty"` // 提示评估耗时
EvalCount int `json:"eval_count,omitempty"` // 评估计数
EvalDuration int64 `json:"eval_duration,omitempty"` // 评估耗时
Response string `json:"response,omitempty"` // 响应内容(流式响应时)
}
// Choice 选择 - OpenAI格式
type Choice struct {
Index int `json:"index"` // 索引
Message ChatMessage `json:"message"` // 消息
FinishReason string `json:"finish_reason"` // 结束原因
}
// Usage 使用情况
type Usage struct {
PromptTokens int `json:"prompt_tokens"` // 提示token数
CompletionTokens int `json:"completion_tokens"` // 完成token数
TotalTokens int `json:"total_tokens"` // 总token数
}
// NewLLMClient 创建LLM客户端
func NewLLMClient(config LLMConfig) *LLMClient {
httpClient := NewHTTPClient()
return &LLMClient{
httpClient: httpClient,
config: config,
}
}
// NewOpenAIClient 创建OpenAI客户端
func NewOpenAIClient(apiKey, model string) *LLMClient {
config := LLMConfig{
APIKey: apiKey,
BaseURL: "https://api.openai.com/v1",
Model: model,
Provider: "openai",
Temperature: 0.1,
MaxTokens: 16000,
}
return NewLLMClient(config)
}
// NewOllamaClient 创建Ollama客户端
func NewOllamaClient(baseURL, model string) *LLMClient {
config := LLMConfig{
BaseURL: baseURL,
Model: model,
Provider: "ollama",
Temperature: 0.1,
MaxTokens: 16000,
}
return NewLLMClient(config)
}
// Chat 发送聊天请求
func (c *LLMClient) Chat(messages []ChatMessage) (*ChatResponse, error) {
if c.config.Provider == "ollama" {
// 对于Ollama,我们需要转换格式
return c.chatWithOllama(messages)
} else {
// 对于OpenAI和其他提供商,使用标准格式
request := ChatRequest{
Model: c.config.Model,
Messages: messages,
Temperature: c.config.Temperature,
MaxTokens: c.config.MaxTokens,
}
return c.sendChatRequest(request)
}
}
// chatWithOllama 使用Ollama格式发送请求
func (c *LLMClient) chatWithOllama(messages []ChatMessage) (*ChatResponse, error) {
// 转换消息格式
ollamaMessages := make([]OllamaMessage, len(messages))
for i, msg := range messages {
ollamaMessages[i] = OllamaMessage{
Role: msg.Role,
Content: msg.Content,
}
}
// 创建Ollama请求
request := OllamaRequest{
Model: c.config.Model,
Messages: ollamaMessages,
Stream: false,
Think: false,
Options: OllamaOptions{
Temperature: c.config.Temperature,
MaxTokens: c.config.MaxTokens,
},
}
// 发送Ollama请求
apiURL := c.buildAPIURL("/api/chat")
headers := c.buildHeaders()
response, err := c.httpClient.PostJSON(apiURL, request, headers)
if err != nil {
return nil, fmt.Errorf("发送Ollama请求失败: %v", err)
}
// 解析Ollama响应
var ollamaResp OllamaResponse
err = json.Unmarshal(response, &ollamaResp)
if err != nil {
return nil, fmt.Errorf("解析Ollama响应失败: %v", err)
}
// 转换Ollama响应为统一的ChatResponse格式
chatResponse := &ChatResponse{
ID: "ollama-" + c.config.Model,
Object: "chat.completion",
Model: c.config.Model,
Choices: []Choice{
{
Index: 0,
Message: ChatMessage{
Role: ollamaResp.Message.Role,
Content: ollamaResp.Message.Content,
},
FinishReason: "stop",
},
},
}
return chatResponse, nil
}
// ChatWithSystem 发送带系统消息的聊天请求
func (c *LLMClient) ChatWithSystem(systemPrompt string, userMessage string) (*ChatResponse, error) {
messages := []ChatMessage{
{Role: "system", Content: systemPrompt},
{Role: "user", Content: userMessage},
}
return c.Chat(messages)
}
// ChatConversation 发送多轮对话
func (c *LLMClient) ChatConversation(conversation []ChatMessage) (*ChatResponse, error) {
return c.Chat(conversation)
}
// sendChatRequest 发送标准聊天请求 (OpenAI格式)
func (c *LLMClient) sendChatRequest(request ChatRequest) (*ChatResponse, error) {
apiURL := c.buildAPIURL("/chat/completions")
headers := c.buildHeaders()
response, err := c.httpClient.PostJSON(apiURL, request, headers)
if err != nil {
return nil, fmt.Errorf("发送请求失败: %v", err)
}
var result ChatResponse
err = json.Unmarshal(response, &result)
if err != nil {
return nil, fmt.Errorf("解析响应失败: %v", err)
}
return &result, nil
}
// buildAPIURL 构建API URL
func (c *LLMClient) buildAPIURL(endpoint string) string {
baseURL := strings.TrimSuffix(c.config.BaseURL, "/")
return baseURL + endpoint
}
// buildHeaders 构建请求头
func (c *LLMClient) buildHeaders() map[string]string {
headers := map[string]string{
"Content-Type": "application/json",
}
if c.config.APIKey != "" {
headers["Authorization"] = "Bearer " + c.config.APIKey
}
return headers
}
// SetTemperature 设置温度参数
func (c *LLMClient) SetTemperature(temperature float64) {
c.config.Temperature = temperature
}
// SetMaxTokens 设置最大token数
func (c *LLMClient) SetMaxTokens(maxTokens int) {
c.config.MaxTokens = maxTokens
}
// SetModel 设置模型
func (c *LLMClient) SetModel(model string) {
c.config.Model = model
}
// GetConfig 获取配置
func (c *LLMClient) GetConfig() LLMConfig {
return c.config
}
// CommonConfigs 预定义的常用配置
var CommonConfigs = struct {
OpenAI LLMConfig
Ollama LLMConfig
Groq LLMConfig
DeepSeek LLMConfig
}{
OpenAI: LLMConfig{
BaseURL: "https://api.openai.com/v1",
Provider: "openai",
Temperature: 0.1,
MaxTokens: 16000,
},
Ollama: LLMConfig{
BaseURL: "http://localhost:11434",
Provider: "ollama",
Temperature: 0.1,
MaxTokens: 16000,
},
Groq: LLMConfig{
BaseURL: "https://api.groq.com/openai/v1",
Provider: "groq",
Temperature: 0.1,
MaxTokens: 16000,
},
DeepSeek: LLMConfig{
BaseURL: "https://api.deepseek.com/v1",
Provider: "deepseek",
Temperature: 0.1,
MaxTokens: 16000,
},
}