Repository navigation
Expand file tree
/
Copy pathgollm.go
More file actions
335 lines (289 loc) · 11.9 KB
/
Copy pathgollm.go
File metadata and controls
335 lines (289 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
// Package gollm provides a high-level interface for interacting with various Language Learning Models (LLMs).
// It supports multiple providers including OpenAI, Anthropic, Ollama, and others, with features
// like prompt optimization, caching, and structured output handling.
package gollm
import (
"context"
"fmt"
"github.com/teilomillet/gollm/config"
"github.com/teilomillet/gollm/llm"
"github.com/teilomillet/gollm/providers"
"github.com/teilomillet/gollm/types"
"github.com/teilomillet/gollm/utils"
)
// ToolResult represents the result of executing a tool.
// Use NewToolResult or NewToolError to create instances.
type ToolResult = types.ToolResult
// NewToolResult creates a successful tool result.
func NewToolResult(toolCallID, content string) ToolResult {
return types.NewToolResult(toolCallID, content)
}
// NewToolError creates an error tool result.
func NewToolError(toolCallID, errorMessage string) ToolResult {
return types.NewToolError(toolCallID, errorMessage)
}
// LLM is the interface that wraps the basic LLM operations.
// It extends the base llm.LLM interface with additional functionality specific to gollm,
// providing a comprehensive set of methods for LLM interaction, configuration, and management.
type LLM interface {
llm.LLM // Embed the base interface
// GetPromptJSONSchema returns the JSON schema for prompt validation in byte format.
// The schema can be customized using SchemaOption parameters.
GetPromptJSONSchema(opts ...SchemaOption) ([]byte, error)
// GetProvider returns the name of the current LLM provider (e.g., "openai", "anthropic").
GetProvider() string
// GetModel returns the name of the current model being used.
GetModel() string
// UpdateLogLevel changes the logging verbosity for both gollm and internal LLM operations.
UpdateLogLevel(level LogLevel)
// Debug logs a debug message with optional key-value pairs for detailed logging.
Debug(msg string, keysAndValues ...interface{})
// GetLogLevel returns the current logging verbosity level.
GetLogLevel() LogLevel
// SetOllamaEndpoint configures a custom endpoint for Ollama provider.
// Returns an error if the current provider doesn't support endpoint configuration.
SetOllamaEndpoint(endpoint string) error
// SetSystemPrompt updates the system prompt with caching configuration.
// The cacheType parameter determines how the prompt should be cached.
SetSystemPrompt(prompt string, cacheType CacheType)
// Memory-related methods (only functional when memory is enabled via SetMemory)
// HasMemory returns true if this LLM instance has memory enabled.
HasMemory() bool
// ClearMemory removes all messages from the conversation history.
// Does nothing if memory is not enabled.
ClearMemory()
// GetMemory returns the conversation history as a slice of messages.
// Returns nil if memory is not enabled.
GetMemory() []MemoryMessage
// AddToMemory adds a message to the conversation history.
// Does nothing if memory is not enabled.
AddToMemory(role, content string)
// AddStructuredMessage adds a message with cache control to the conversation history.
// The cacheControl parameter specifies caching behavior (e.g., "ephemeral").
// Does nothing if memory is not enabled.
AddStructuredMessage(role, content, cacheControl string)
// SetUseStructuredMessages configures whether to use structured messages.
// When true (default), messages are passed as separate JSON objects to the provider.
// When false, messages are flattened into a single prompt string.
// Does nothing if memory is not enabled.
SetUseStructuredMessages(use bool)
// Tool calling methods (only functional when memory is enabled via SetMemory)
// AddToolResult adds a tool execution result to the conversation history.
// Use this after executing a tool to provide the result back to the LLM.
// Does nothing if memory is not enabled.
AddToolResult(toolCallID, result string)
// AddToolError adds a tool execution error to the conversation history.
// Use this when a tool fails to inform the LLM of the error.
// Does nothing if memory is not enabled.
AddToolError(toolCallID, errorMessage string)
// AddAssistantMessageWithToolCalls adds an assistant message with tool calls.
// This preserves tool calls in the conversation for multi-turn tool usage.
// Does nothing if memory is not enabled.
AddAssistantMessageWithToolCalls(content string, toolCalls []ToolCall)
}
// llmImpl is the concrete implementation of the LLM interface.
// It wraps the base LLM implementation and adds provider-specific functionality,
// logging capabilities, and configuration management.
type llmImpl struct {
llm.LLM
provider providers.Provider
logger utils.Logger
model string
config *config.Config
}
// SetSystemPrompt sets the system prompt for the LLM.
func (l *llmImpl) SetSystemPrompt(prompt string, cacheType CacheType) {
newPrompt := NewPrompt(prompt, WithSystemPrompt(prompt, cacheType))
l.SetOption("system_prompt", newPrompt)
}
// HasMemory returns true if this LLM instance has memory enabled.
func (l *llmImpl) HasMemory() bool {
_, ok := l.LLM.(llm.MemoryCapable)
return ok
}
// ClearMemory removes all messages from the conversation history.
func (l *llmImpl) ClearMemory() {
if mem, ok := l.LLM.(llm.MemoryCapable); ok {
mem.ClearMemory()
}
}
// GetMemory returns the conversation history as a slice of messages.
func (l *llmImpl) GetMemory() []MemoryMessage {
if mem, ok := l.LLM.(llm.MemoryCapable); ok {
return mem.GetMemory()
}
return nil
}
// AddToMemory adds a message to the conversation history.
func (l *llmImpl) AddToMemory(role, content string) {
if mem, ok := l.LLM.(llm.MemoryCapable); ok {
mem.AddToMemory(role, content)
}
}
// AddStructuredMessage adds a message with cache control to the conversation history.
func (l *llmImpl) AddStructuredMessage(role, content, cacheControl string) {
if mem, ok := l.LLM.(llm.MemoryCapable); ok {
mem.AddStructuredMessage(role, content, cacheControl)
}
}
// SetUseStructuredMessages configures whether to use structured messages.
func (l *llmImpl) SetUseStructuredMessages(use bool) {
if mem, ok := l.LLM.(llm.MemoryCapable); ok {
mem.SetUseStructuredMessages(use)
}
}
// AddToolResult adds a tool execution result to the conversation history.
func (l *llmImpl) AddToolResult(toolCallID, result string) {
if mem, ok := l.LLM.(llm.MemoryCapable); ok {
mem.AddToolResult(toolCallID, result)
}
}
// AddToolError adds a tool execution error to the conversation history.
func (l *llmImpl) AddToolError(toolCallID, errorMessage string) {
if mem, ok := l.LLM.(llm.MemoryCapable); ok {
mem.AddToolError(toolCallID, errorMessage)
}
}
// AddAssistantMessageWithToolCalls adds an assistant message with tool calls.
func (l *llmImpl) AddAssistantMessageWithToolCalls(content string, toolCalls []ToolCall) {
if mem, ok := l.LLM.(llm.MemoryCapable); ok {
mem.AddAssistantMessageWithToolCalls(content, toolCalls)
}
}
// GetProvider returns the provider of the LLM.
func (l *llmImpl) GetProvider() string {
return l.provider.Name()
}
// GetModel returns the model of the LLM.
func (l *llmImpl) GetModel() string {
return l.model
}
// Debug logs a debug message with optional key-value pairs.
func (l *llmImpl) Debug(msg string, keysAndValues ...interface{}) {
l.logger.Debug(msg, keysAndValues...)
}
// GetLogLevel returns the current log level of the LLM.
func (l *llmImpl) GetLogLevel() LogLevel {
return LogLevel(l.config.LogLevel)
}
// SetOption sets an option for the LLM with the given key and value.
func (l *llmImpl) SetOption(key string, value interface{}) {
l.logger.Debug("Setting option", "key", key, "value", value)
l.LLM.SetOption(key, value)
l.logger.Debug("Option set successfully")
}
func (l *llmImpl) SetOllamaEndpoint(endpoint string) error {
if p, ok := l.provider.(interface{ SetEndpoint(string) }); ok {
p.SetEndpoint(endpoint)
return nil
}
return fmt.Errorf("current provider does not support setting custom endpoint")
}
// GetPromptJSONSchema generates and returns the JSON schema for the Prompt.
func (l *llmImpl) GetPromptJSONSchema(opts ...SchemaOption) ([]byte, error) {
p := &Prompt{}
return p.GenerateJSONSchema(opts...)
}
// UpdateLogLevel updates the log level for both the gollm package and the internal llm package.
func (l *llmImpl) UpdateLogLevel(level LogLevel) {
l.config.LogLevel = utils.LogLevel(level)
l.logger.SetLevel(utils.LogLevel(level))
if internalLLM, ok := l.LLM.(interface{ SetLogLevel(utils.LogLevel) }); ok {
internalLLM.SetLogLevel(utils.LogLevel(level))
}
}
// Implement the base Generate method (if not already provided by embedded llm.LLM)
func (l *llmImpl) Generate(ctx context.Context, prompt *llm.Prompt, opts ...llm.GenerateOption) (string, error) {
l.logger.Debug("Starting Generate method", "prompt_length", len(prompt.String()), "context", ctx)
config := &llm.GenerateConfig{}
for _, opt := range opts {
opt(config)
}
if config.UseJSONSchema {
if err := prompt.Validate(); err != nil {
return "", fmt.Errorf("invalid prompt: %w", err)
}
}
// Call the base LLM's Generate method
response, err := l.LLM.Generate(ctx, prompt, opts...)
if err != nil {
return "", fmt.Errorf("LLM.Generate error: %w", err)
}
return response, nil
}
// NewLLM creates a new LLM instance with the specified configuration options.
// It supports memory management, caching, and provider-specific optimizations.
// If memory options are provided, it creates an LLM instance with conversation memory.
//
// The function performs the following setup:
// 1. Loads and applies configuration from both default and provided options
// 2. Initializes logging system with appropriate verbosity
// 3. Sets up provider-specific optimizations (e.g., Anthropic caching headers)
// 4. Creates and configures the base LLM instance
// 5. Optionally enables conversation memory if specified in config
//
// Returns an error if:
// - Configuration loading fails
// - Provider initialization fails
// - Memory setup fails (if memory option is enabled)
func NewLLM(opts ...ConfigOption) (LLM, error) {
cfg, err := LoadConfig()
if err != nil {
return nil, fmt.Errorf("failed to load config: %w", err)
}
for _, opt := range opts {
opt(cfg)
}
// For local LLM servers (Ollama, LM Studio, vLLM), ensure we have a dummy API key
if cfg.Provider == "ollama" || cfg.Provider == "lmstudio" || cfg.Provider == "vllm" {
if cfg.APIKeys == nil {
cfg.APIKeys = make(map[string]string)
}
if _, exists := cfg.APIKeys[cfg.Provider]; !exists || cfg.APIKeys[cfg.Provider] == "" {
cfg.APIKeys[cfg.Provider] = cfg.Provider + "-local"
}
}
// Validate config (with custom validator if provided)
if err := llm.ValidateWithCustomValidator(cfg, cfg.CustomValidator); err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}
// Use custom logger if provided, otherwise create default
var logger utils.Logger
if cfg.Logger != nil {
logger = cfg.Logger
} else {
logger = utils.NewLogger(cfg.LogLevel)
}
if cfg.Provider == "anthropic" && cfg.EnableCaching {
if cfg.ExtraHeaders == nil {
cfg.ExtraHeaders = make(map[string]string)
}
cfg.ExtraHeaders["anthropic-beta"] = "prompt-caching-2024-07-31"
}
registry := providers.GetDefaultRegistry()
baseLLM, err := llm.NewLLM(cfg, logger, registry)
if err != nil {
logger.Error("Failed to create internal LLM", "error", err)
return nil, fmt.Errorf("failed to create internal LLM: %w", err)
}
provider, err := registry.Get(cfg.Provider, cfg.APIKeys[cfg.Provider], cfg.Model, cfg.ExtraHeaders)
if err != nil {
return nil, fmt.Errorf("failed to get provider: %w", err)
}
llmInstance := &llmImpl{
LLM: baseLLM,
provider: provider,
logger: logger,
model: cfg.Model,
config: cfg,
}
if cfg.MemoryOption != nil {
llmWithMemory, err := llm.NewLLMWithMemory(baseLLM, cfg.MemoryOption.MaxTokens, cfg.Model)
if err != nil {
logger.Error("Failed to create LLM with memory", "error", err)
return nil, fmt.Errorf("failed to create LLM with memory: %w", err)
}
llmInstance.LLM = llmWithMemory
}
return llmInstance, nil
}