Skip to content

Commit 7109f52

Browse files
refactor: simplify codebase with shared helpers and reduced duplication
- Add logf() and tryResetInterpreter() helpers in RLM - Extract prepareMessages() in Anthropic/Gemini providers - Consolidate truncate functions to use core.Truncate() - Simplify NewPooled() by removing ineffective pool logic - Add iterationState and executeCodeBlocks() shared abstractions - Organize magic numbers into named constants
1 parent 495d3e8 commit 7109f52

6 files changed

Lines changed: 322 additions & 483 deletions

File tree

‎cmd/benchmark/main.go‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -373,12 +373,9 @@ func loadTasks(path string) ([]Task, error) {
373373
return tasks, nil
374374
}
375375

376-
// truncate truncates a string to maxLen characters.
376+
// truncate truncates a string to maxLen characters (uses core.Truncate).
377377
func truncate(s string, maxLen int) string {
378-
if len(s) <= maxLen {
379-
return s
380-
}
381-
return s[:maxLen] + "..."
378+
return core.Truncate(s, maxLen)
382379
}
383380

384381
func main() {

‎pkg/core/types.go‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,16 @@ func (e *DepthExceededError) Error() string {
126126
e.CurrentDepth, e.MaxDepth, truncatePrompt(e.Prompt, 50))
127127
}
128128

129-
// truncatePrompt truncates a prompt for error messages.
130-
func truncatePrompt(s string, maxLen int) string {
129+
// Truncate shortens a string to maxLen characters, adding "..." suffix if truncated.
130+
// This is a utility function used for logging and error messages.
131+
func Truncate(s string, maxLen int) string {
131132
if len(s) <= maxLen {
132133
return s
133134
}
134135
return s[:maxLen] + "..."
135136
}
137+
138+
// truncatePrompt truncates a prompt for error messages (internal alias for Truncate).
139+
func truncatePrompt(s string, maxLen int) string {
140+
return Truncate(s, maxLen)
141+
}

‎pkg/providers/anthropic.go‎

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ type streamEvent struct {
132132
} `json:"usage,omitempty"`
133133
}
134134

135-
// Complete implements rlm.LLMClient for root LLM orchestration.
136-
func (c *AnthropicClient) Complete(ctx context.Context, messages []core.Message) (core.LLMResponse, error) {
135+
// prepareMessages extracts system prompt and converts messages to API format.
136+
func (c *AnthropicClient) prepareMessages(messages []core.Message) (string, []anthropicMessage) {
137137
var systemPrompt string
138138
var apiMessages []anthropicMessage
139139

@@ -147,6 +147,12 @@ func (c *AnthropicClient) Complete(ctx context.Context, messages []core.Message)
147147
})
148148
}
149149
}
150+
return systemPrompt, apiMessages
151+
}
152+
153+
// Complete implements rlm.LLMClient for root LLM orchestration.
154+
func (c *AnthropicClient) Complete(ctx context.Context, messages []core.Message) (core.LLMResponse, error) {
155+
systemPrompt, apiMessages := c.prepareMessages(messages)
150156

151157
reqBody := anthropicRequest{
152158
Model: c.model,
@@ -315,19 +321,7 @@ func (c *AnthropicClient) doRequest(ctx context.Context, reqBody anthropicReques
315321
// The handler is called for each chunk of content as it arrives.
316322
// Returns the complete response with token usage after stream completes.
317323
func (c *AnthropicClient) CompleteStream(ctx context.Context, messages []core.Message, handler StreamHandler) (core.LLMResponse, error) {
318-
var systemPrompt string
319-
var apiMessages []anthropicMessage
320-
321-
for _, msg := range messages {
322-
if msg.Role == "system" {
323-
systemPrompt = msg.Content
324-
} else {
325-
apiMessages = append(apiMessages, anthropicMessage{
326-
Role: msg.Role,
327-
Content: msg.Content,
328-
})
329-
}
330-
}
324+
systemPrompt, apiMessages := c.prepareMessages(messages)
331325

332326
reqBody := anthropicRequest{
333327
Model: c.model,

‎pkg/providers/gemini.go‎

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ type geminiResponse struct {
101101
} `json:"error,omitempty"`
102102
}
103103

104-
// Complete implements rlm.LLMClient for root LLM orchestration.
105-
func (c *GeminiClient) Complete(ctx context.Context, messages []core.Message) (core.LLMResponse, error) {
104+
// prepareMessages extracts system instruction and converts messages to API format.
105+
func (c *GeminiClient) prepareMessages(messages []core.Message) (*geminiContent, []geminiContent) {
106106
var systemContent *geminiContent
107107
var contents []geminiContent
108108

@@ -122,6 +122,12 @@ func (c *GeminiClient) Complete(ctx context.Context, messages []core.Message) (c
122122
})
123123
}
124124
}
125+
return systemContent, contents
126+
}
127+
128+
// Complete implements rlm.LLMClient for root LLM orchestration.
129+
func (c *GeminiClient) Complete(ctx context.Context, messages []core.Message) (core.LLMResponse, error) {
130+
systemContent, contents := c.prepareMessages(messages)
125131

126132
reqBody := geminiRequest{
127133
Contents: contents,
@@ -299,25 +305,7 @@ type geminiStreamChunk struct {
299305
// The handler is called for each chunk of content as it arrives.
300306
// Returns the complete response with token usage after stream completes.
301307
func (c *GeminiClient) CompleteStream(ctx context.Context, messages []core.Message, handler StreamHandler) (core.LLMResponse, error) {
302-
var systemContent *geminiContent
303-
var contents []geminiContent
304-
305-
for _, msg := range messages {
306-
if msg.Role == "system" {
307-
systemContent = &geminiContent{
308-
Parts: []geminiPart{{Text: msg.Content}},
309-
}
310-
} else {
311-
role := msg.Role
312-
if role == "assistant" {
313-
role = "model"
314-
}
315-
contents = append(contents, geminiContent{
316-
Role: role,
317-
Parts: []geminiPart{{Text: msg.Content}},
318-
})
319-
}
320-
}
308+
systemContent, contents := c.prepareMessages(messages)
321309

322310
reqBody := geminiRequest{
323311
Contents: contents,

‎pkg/repl/repl.go‎

Lines changed: 9 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -94,26 +94,9 @@ func (p *REPLPool) Stats() (poolSize, totalCreated int) {
9494
return len(p.pool), p.created
9595
}
9696

97-
// interpreterPool is a simple sync.Pool for basic interpreter reuse.
98-
// Note: Due to Yaegi limitations, interpreters can't be fully reset,
99-
// so this primarily helps with pre-warming the GC and memory allocation.
100-
var interpreterPool = sync.Pool{
101-
New: func() interface{} {
102-
stdout := new(bytes.Buffer)
103-
stderr := new(bytes.Buffer)
104-
105-
i := interp.New(interp.Options{
106-
Stdout: stdout,
107-
Stderr: stderr,
108-
})
109-
110-
if err := i.Use(stdlib.Symbols); err != nil {
111-
panic(fmt.Sprintf("failed to load stdlib: %v", err))
112-
}
113-
114-
return i
115-
},
116-
}
97+
// Note: Yaegi interpreters cannot be reset to a clean state, so traditional
98+
// pooling (reusing the same interpreter across requests) is not possible.
99+
// See REPLPool for a pre-creation approach that helps with startup latency.
117100

118101
// QueryResponse contains the LLM response with usage metadata.
119102
type QueryResponse struct {
@@ -205,49 +188,13 @@ func New(client LLMClient, opts ...REPLOption) *REPL {
205188
return r
206189
}
207190

208-
// NewPooled creates a new REPL instance using the interpreter pool.
209-
// This eliminates the 10-50ms startup overhead per REPL instance.
210-
// Call Close() when done to return the interpreter to the pool.
191+
// NewPooled creates a new REPL instance with pooling flags set.
192+
// Note: Due to Yaegi interpreter limitations (can't be reset to clean state),
193+
// this function simply creates a fresh REPL each time. The pooling flags are
194+
// preserved for compatibility, but actual interpreter reuse is not possible.
195+
// Consider using REPLPool for pre-created REPL instances if startup time matters.
211196
func NewPooled(client LLMClient) *REPL {
212-
stdout := new(bytes.Buffer)
213-
stderr := new(bytes.Buffer)
214-
215-
// Get interpreter from pool (will be created if pool is empty)
216-
pooledInterp := interpreterPool.Get().(*interp.Interpreter)
217-
218-
// Create a new interpreter with fresh buffers
219-
// Note: We can't reuse the pooled interpreter's buffers as they may contain stale data
220-
// and we can't safely redirect them. Instead, we create a fresh interpreter.
221-
i := interp.New(interp.Options{
222-
Stdout: stdout,
223-
Stderr: stderr,
224-
})
225-
226-
// Load standard library
227-
if err := i.Use(stdlib.Symbols); err != nil {
228-
panic(fmt.Sprintf("failed to load stdlib: %v", err))
229-
}
230-
231-
// Return the pooled interpreter since we can't fully reuse it
232-
interpreterPool.Put(pooledInterp)
233-
234-
r := &REPL{
235-
interp: i,
236-
stdout: stdout,
237-
stderr: stderr,
238-
llmClient: client,
239-
ctx: context.Background(),
240-
fromPool: true,
241-
usePooling: true,
242-
asyncQueries: make(map[string]*AsyncQueryHandle),
243-
}
244-
245-
// Inject RLM functions
246-
if err := r.injectBuiltins(); err != nil {
247-
panic(fmt.Sprintf("failed to inject builtins: %v", err))
248-
}
249-
250-
return r
197+
return New(client, WithPooling(true))
251198
}
252199

253200
// Close releases resources and returns the interpreter to the pool if applicable.

0 commit comments

Comments
 (0)