Drop the github.com/sashabaranov/go-openai dependency in favor of an internal HTTP client. The LLM package is now split into: - llm.go: type definitions (Message, ChatRequest, ChatResponse, etc.) - client.go: client construction and configuration - client_completions.go: Completions and CompletionsStream methods - client_models.go: ListModels - client_tools.go: tool call execution CompletionsStream uses typed SSE events (delta, done, message) so the frontend can render progressive streaming output. Move tool.go and tool_test.go from internal/tool/ into internal/llm/ to co-locate the registry with the client that consumes it. Update job.go and service.go to use the new llm.Message types and Completions/CompletionsStream methods. Remove the Voice field from chat request/response (voice is now tracked via message metadata). Frontend: handle delta/done/message SSE events, render a streaming placeholder that receives progressive text deltas, and finalize with full message binding (reasoning, tool calls, actions).
83 lines
1.9 KiB
Go
83 lines
1.9 KiB
Go
package llm
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// Config holds the configuration for an LLM client.
|
|
type Config struct {
|
|
// Key is the API key for authentication.
|
|
Key string `yaml:"key"`
|
|
// SystemMessage is prepended to all conversations.
|
|
SystemMessage string `yaml:"system_message"`
|
|
// Timeout is the maximum duration for a query (e.g., "5m").
|
|
// Defaults to 5 minutes if empty.
|
|
Timeout string `yaml:"timeout"`
|
|
// URL is the base URL of the OpenAI-compatible API endpoint.
|
|
URL string `yaml:"url"`
|
|
}
|
|
|
|
// Validate checks that required configuration values are present and valid.
|
|
func (cfg Config) Validate() error {
|
|
if cfg.Timeout != "" {
|
|
if _, err := time.ParseDuration(cfg.Timeout); err != nil {
|
|
return fmt.Errorf("invalid timeout: %w", err)
|
|
}
|
|
}
|
|
if cfg.URL == "" {
|
|
return fmt.Errorf("missing URL")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Client wraps an OpenAI-compatible client with tool execution support.
|
|
type Client struct {
|
|
baseURL string
|
|
httpc *http.Client
|
|
key string
|
|
log *slog.Logger
|
|
registry *Registry
|
|
systemMessage string
|
|
timeout time.Duration
|
|
tools []APITool
|
|
}
|
|
|
|
// NewClient creates a new LLM client with the provided configuration.
|
|
// The registry is optional; if nil, tool calling is disabled.
|
|
func NewClient(
|
|
cfg Config,
|
|
registry *Registry,
|
|
log *slog.Logger,
|
|
) (*Client, error) {
|
|
if err := cfg.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid config: %w", err)
|
|
}
|
|
|
|
timeout := 5 * time.Minute
|
|
if cfg.Timeout != "" {
|
|
d, err := time.ParseDuration(cfg.Timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse timeout: %v", err)
|
|
}
|
|
timeout = d
|
|
}
|
|
|
|
llm := &Client{
|
|
baseURL: cfg.URL,
|
|
key: cfg.Key,
|
|
log: log,
|
|
systemMessage: cfg.SystemMessage,
|
|
registry: registry,
|
|
httpc: &http.Client{},
|
|
timeout: timeout,
|
|
}
|
|
|
|
// Convert tools from registry.
|
|
llm.tools = registry.ToAPI()
|
|
|
|
return llm, nil
|
|
}
|