// Package llm 提供大模型通信能力(含视觉与流式), // 支持 OpenAI Chat Completions 与 Anthropic Messages 两种接口格式。 package llm import ( "context" "fmt" "net/http" "time" ) // 接口格式。它决定请求体结构、鉴权头与流式事件的解析方式, // 也是配置里唯一需要用户先选的东西。 const ( // FormatOpenAI 是 OpenAI Chat Completions 兼容格式(POST {base}/chat/completions)。 FormatOpenAI = "openai" // FormatAnthropic 是 Anthropic Messages 兼容格式(POST {base}/v1/messages)。 FormatAnthropic = "anthropic" ) // FormatLabel 返回接口格式的中文名,供界面显示。 func FormatLabel(format string) string { switch format { case FormatAnthropic: return "Anthropic 兼容" default: return "OpenAI 兼容" } } // Formats 返回全部受支持的接口格式,顺序即界面下拉顺序。 func Formats() []string { return []string{FormatOpenAI, FormatAnthropic} } // Role 是消息角色。 type Role string // 支持的角色。 const ( RoleUser Role = "user" RoleAssistant Role = "assistant" ) // Message 是一条对话消息。Image 非空表示该消息附带一张 JPEG 图片。 type Message struct { Role Role Text string Image []byte } // Usage 记录一次调用的 token 用量。 type Usage struct { PromptTokens int CompletionTokens int TotalTokens int } // Add 累加用量。 func (u *Usage) Add(o Usage) { u.PromptTokens += o.PromptTokens u.CompletionTokens += o.CompletionTokens u.TotalTokens += o.TotalTokens } // Delta 是一次流式增量。 // Text 是正式回答,Reasoning 是模型吐出来的思考过程;两者都按原样透传, // 服务端吐什么、界面就显示什么。 type Delta struct { Text string Reasoning string } // DeltaFunc 接收流式增量,返回 error 可中断请求。 type DeltaFunc func(delta Delta) error // Options 描述一个模型的接入参数。 type Options struct { Name string Format string BaseURL string APIKey string Model string ExtraHeaders map[string]string MaxTokens int Temperature float64 } // Provider 是大模型服务的最小抽象。 type Provider interface { // Chat 发起一次流式对话,system 为系统提示词。 Chat(ctx context.Context, system string, msgs []Message, fn DeltaFunc) (Usage, error) // Ping 做一次最小代价的连通性检查。 Ping(ctx context.Context) error } // New 根据模型配置创建 Provider,按 Format 分派到对应协议实现。 func New(opts Options) (Provider, error) { if opts.BaseURL == "" { return nil, fmt.Errorf("模型配置缺少 BaseURL") } if opts.Model == "" { return nil, fmt.Errorf("模型 %s 缺少模型名", opts.Name) } switch opts.Format { case "", FormatOpenAI: return newOpenAI(opts), nil case FormatAnthropic: return newAnthropic(opts), nil default: return nil, fmt.Errorf("未知的接口格式 %q", opts.Format) } } // newHTTPClient 返回不设总超时的客户端:流式响应可能持续很久,由 ctx 控制。 func newHTTPClient() *http.Client { return &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, MaxIdleConns: 8, IdleConnTimeout: 90 * time.Second, ForceAttemptHTTP2: true, TLSHandshakeTimeout: 15 * time.Second, }, } }