Skip to content

Commit d50afa9

Browse files
fix: resolve CI failures and add benchmark CLI features
- Fix Gemini streaming hang by detecting finishReason completion signal - Fix sandbox TestNewExecutorWithAuto by disabling IPC in CI environment - Fix lint issues in performance_benchmark_test.go (unused variables, fmt.Sprintf) - Add adaptive iteration flags (-adaptive, -base-iters, -max-iters, -early-termination) - Add recursion depth flag (-recursion-depth) to benchmark CLI - Convert export_oolong.py to uv inline script (PEP 723)
1 parent 7f8d427 commit d50afa9

6 files changed

Lines changed: 103 additions & 34 deletions

File tree

‎cmd/benchmark/export_oolong.py‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
#!/usr/bin/env python3
1+
#!/usr/bin/env -S uv run
2+
# /// script
3+
# requires-python = ">=3.10"
4+
# dependencies = [
5+
# "datasets",
6+
# ]
7+
# ///
28
"""Export OOLONG dataset to JSON for Go benchmark."""
39

410
import json
511
import sys
6-
from pathlib import Path
12+
13+
from datasets import load_dataset # noqa: E402 - managed by uv
714

815

916
def main():
10-
try:
11-
from datasets import load_dataset
12-
except ImportError:
13-
print("Error: datasets library not installed")
14-
print("Install with: pip install datasets")
15-
sys.exit(1)
1617

1718
num_tasks = int(sys.argv[1]) if len(sys.argv) > 1 else 10
1819
output_file = sys.argv[2] if len(sys.argv) > 2 else "oolong_tasks.json"

‎cmd/benchmark/main.go‎

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,18 @@ func runBaseline(ctx context.Context, task Task, client providers.Client) Benchm
198198

199199
// RLMOptions holds configuration for RLM runs.
200200
type RLMOptions struct {
201-
LogDir string
202-
Verbose bool
203-
Streaming bool
204-
Pool *repl.REPLPool
205-
Compression bool
206-
VerbatimIters int
207-
Backend string
201+
LogDir string
202+
Verbose bool
203+
Streaming bool
204+
Pool *repl.REPLPool
205+
Compression bool
206+
VerbatimIters int
207+
Backend string
208+
Adaptive bool
209+
BaseIters int
210+
MaxIters int
211+
EarlyTerminate bool
212+
RecursionDepth int
208213
}
209214

210215
// ModelClient is an interface for clients that expose their model name.
@@ -240,7 +245,7 @@ func runRLM(ctx context.Context, task Task, client providers.Client, opts RLMOpt
240245

241246
// Build RLM options
242247
rlmOpts := []rlm.Option{
243-
rlm.WithMaxIterations(30),
248+
rlm.WithMaxIterations(opts.MaxIters),
244249
rlm.WithVerbose(opts.Verbose),
245250
rlm.WithLogger(log),
246251
}
@@ -257,6 +262,21 @@ func runRLM(ctx context.Context, task Task, client providers.Client, opts RLMOpt
257262
rlmOpts = append(rlmOpts, rlm.WithHistoryCompression(opts.VerbatimIters, 500))
258263
}
259264

265+
if opts.Adaptive {
266+
rlmOpts = append(rlmOpts, rlm.WithAdaptiveIterationConfig(rlm.AdaptiveIterationConfig{
267+
Enabled: true,
268+
BaseIterations: opts.BaseIters,
269+
MaxIterations: opts.MaxIters,
270+
ContextScaleFactor: 100000, // 100KB per additional iteration
271+
EnableEarlyTermination: opts.EarlyTerminate,
272+
ConfidenceThreshold: 1,
273+
}))
274+
}
275+
276+
if opts.RecursionDepth > 0 {
277+
rlmOpts = append(rlmOpts, rlm.WithMaxRecursionDepth(opts.RecursionDepth))
278+
}
279+
260280
r := rlm.New(client, client, rlmOpts...)
261281

262282
start := time.Now()
@@ -370,6 +390,11 @@ func main() {
370390
verbatimIters = flag.Int("verbatim-iters", 3, "Keep last N iterations verbatim (requires -compression)")
371391
enablePrefixCaching = flag.Bool("prefix-caching", true, "Enable Anthropic prefix caching")
372392
enableAsync = flag.Bool("async", false, "Use async sub-LLM queries where possible")
393+
enableAdaptive = flag.Bool("adaptive", false, "Enable adaptive iteration strategy")
394+
baseIters = flag.Int("base-iters", 10, "Base iterations for adaptive mode")
395+
maxIters = flag.Int("max-iters", 30, "Maximum iterations (used in both adaptive and fixed modes)")
396+
enableEarlyTerm = flag.Bool("early-termination", true, "Enable early termination on confidence (requires -adaptive)")
397+
recursionDepth = flag.Int("recursion-depth", 1, "Max recursion depth for multi-depth RLM (0=disabled, 1=paper default)")
373398
)
374399
flag.Parse()
375400

@@ -410,6 +435,7 @@ func main() {
410435
fmt.Printf("Provider: %s\n", provider)
411436
fmt.Printf("Model: %s\n", *model)
412437
fmt.Printf("Tasks: %d\n", len(tasks))
438+
fmt.Printf("Max Iterations: %d\n", *maxIters)
413439
if *enableStreaming {
414440
fmt.Println("Streaming: enabled")
415441
}
@@ -419,6 +445,12 @@ func main() {
419445
if *enableCompression {
420446
fmt.Printf("History Compression: enabled (verbatim=%d)\n", *verbatimIters)
421447
}
448+
if *enableAdaptive {
449+
fmt.Printf("Adaptive Iteration: enabled (base=%d, max=%d, early-term=%v)\n", *baseIters, *maxIters, *enableEarlyTerm)
450+
}
451+
if *recursionDepth > 0 {
452+
fmt.Printf("Multi-Depth Recursion: enabled (max-depth=%d)\n", *recursionDepth)
453+
}
422454
if *enablePrefixCaching && provider == providers.Anthropic {
423455
fmt.Println("Prefix Caching: enabled (Anthropic)")
424456
} else if provider == providers.Anthropic {
@@ -454,13 +486,18 @@ func main() {
454486

455487
// Build RLM options
456488
rlmOpts := RLMOptions{
457-
LogDir: *logDir,
458-
Verbose: *verbose,
459-
Streaming: *enableStreaming,
460-
Pool: pool,
461-
Compression: *enableCompression,
462-
VerbatimIters: *verbatimIters,
463-
Backend: string(provider),
489+
LogDir: *logDir,
490+
Verbose: *verbose,
491+
Streaming: *enableStreaming,
492+
Pool: pool,
493+
Compression: *enableCompression,
494+
VerbatimIters: *verbatimIters,
495+
Backend: string(provider),
496+
Adaptive: *enableAdaptive,
497+
BaseIters: *baseIters,
498+
MaxIters: *maxIters,
499+
EarlyTerminate: *enableEarlyTerm,
500+
RecursionDepth: *recursionDepth,
464501
}
465502

466503
var baselineResults []BenchmarkResult
@@ -554,6 +591,16 @@ func main() {
554591
output := map[string]any{
555592
"model": *model,
556593
"prefix_caching": *enablePrefixCaching,
594+
"streaming": *enableStreaming,
595+
"pooling": *enablePooling,
596+
"pool_size": *poolSize,
597+
"compression": *enableCompression,
598+
"verbatim_iters": *verbatimIters,
599+
"adaptive": *enableAdaptive,
600+
"base_iters": *baseIters,
601+
"max_iters": *maxIters,
602+
"early_termination": *enableEarlyTerm,
603+
"recursion_depth": *recursionDepth,
557604
"baseline_results": baselineResults,
558605
"rlm_results": rlmResults,
559606
"baseline_summary": baselineSummary,

‎pkg/benchmark/performance_benchmark_test.go‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package benchmark
44

55
import (
66
"context"
7-
"fmt"
87
"os"
98
"strings"
109
"sync"
@@ -189,8 +188,8 @@ func TestStreamingVsNonStreaming(t *testing.T) {
189188
avgFirstChunk := avgDuration(firstChunkTimes)
190189

191190
t.Logf("\n=== Streaming vs Non-Streaming ===")
192-
t.Logf("Non-streaming avg: %v", avgNonStreaming)
193-
t.Logf("Streaming avg: %v", avgStreaming)
191+
t.Logf("Non-streaming avg: %v (tokens: %v)", avgNonStreaming, nonStreamingTokens)
192+
t.Logf("Streaming avg: %v (tokens: %v)", avgStreaming, streamingTokens)
194193
t.Logf("First chunk avg: %v", avgFirstChunk)
195194
if avgNonStreaming > 0 {
196195
improvement := float64(avgNonStreaming-avgFirstChunk) / float64(avgNonStreaming) * 100
@@ -364,6 +363,7 @@ func TestGeminiImplicitCaching(t *testing.T) {
364363
}
365364

366365
t.Logf("\n=== Gemini Implicit Caching ===")
366+
t.Logf("Total tokens across runs: %v", totalTokens)
367367
t.Logf("Cache read tokens across runs: %v", cacheReadTokens)
368368
t.Logf("Timing across runs: %v", timings)
369369

@@ -740,13 +740,13 @@ func TestIdentifyBottlenecks(t *testing.T) {
740740
execTimes := make([]time.Duration, 5)
741741
for i := 0; i < 5; i++ {
742742
start := time.Now()
743-
code := fmt.Sprintf(`
743+
code := `
744744
sum := 0
745745
for j := 0; j < 1000; j++ {
746746
sum += j
747747
}
748748
fmt.Println(sum)
749-
`)
749+
`
750750
_, _ = r.Execute(context.Background(), code)
751751
execTimes[i] = time.Since(start)
752752
}

‎pkg/providers/gemini.go‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,14 @@ func (c *GeminiClient) doRequest(ctx context.Context, reqBody geminiRequest) (st
209209
return "", empty, fmt.Errorf("marshal request: %w", err)
210210
}
211211

212-
url := fmt.Sprintf("%s/v1beta/models/%s:generateContent?key=%s", c.baseURL, c.model, c.apiKey)
212+
url := fmt.Sprintf("%s/v1beta/models/%s:generateContent", c.baseURL, c.model)
213213
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonBody))
214214
if err != nil {
215215
return "", empty, fmt.Errorf("create request: %w", err)
216216
}
217217

218218
req.Header.Set("Content-Type", "application/json")
219+
req.Header.Set("x-goog-api-key", c.apiKey)
219220

220221
resp, err := c.httpClient.Do(req)
221222
if err != nil {
@@ -335,13 +336,14 @@ func (c *GeminiClient) doStreamRequest(ctx context.Context, reqBody geminiReques
335336
return core.LLMResponse{}, fmt.Errorf("marshal request: %w", err)
336337
}
337338

338-
url := fmt.Sprintf("%s/v1beta/models/%s:streamGenerateContent?alt=sse&key=%s", c.baseURL, c.model, c.apiKey)
339+
url := fmt.Sprintf("%s/v1beta/models/%s:streamGenerateContent?alt=sse", c.baseURL, c.model)
339340
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonBody))
340341
if err != nil {
341342
return core.LLMResponse{}, fmt.Errorf("create request: %w", err)
342343
}
343344

344345
req.Header.Set("Content-Type", "application/json")
346+
req.Header.Set("x-goog-api-key", c.apiKey)
345347

346348
resp, err := c.httpClient.Do(req)
347349
if err != nil {
@@ -356,6 +358,7 @@ func (c *GeminiClient) doStreamRequest(ctx context.Context, reqBody geminiReques
356358

357359
var fullContent strings.Builder
358360
var promptTokens, completionTokens, cachedContentTokens int
361+
var streamCompleted bool
359362

360363
scanner := bufio.NewScanner(resp.Body)
361364
buf := make([]byte, 0, 64*1024)
@@ -410,13 +413,30 @@ func (c *GeminiClient) doStreamRequest(ctx context.Context, reqBody geminiReques
410413
if chunk.UsageMetadata.CachedContentTokenCount > 0 {
411414
cachedContentTokens = chunk.UsageMetadata.CachedContentTokenCount
412415
}
416+
417+
// Check for stream completion signal from Gemini
418+
for _, candidate := range chunk.Candidates {
419+
if candidate.FinishReason == "STOP" || candidate.FinishReason == "MAX_TOKENS" ||
420+
candidate.FinishReason == "SAFETY" || candidate.FinishReason == "RECITATION" {
421+
// Stream complete - signal handler and exit loop
422+
streamCompleted = true
423+
if handler != nil {
424+
if err := handler("", true); err != nil {
425+
return core.LLMResponse{}, fmt.Errorf("handler error: %w", err)
426+
}
427+
}
428+
goto streamDone
429+
}
430+
}
413431
}
414432

433+
streamDone:
415434
if err := scanner.Err(); err != nil {
416435
return core.LLMResponse{}, fmt.Errorf("scanner error: %w", err)
417436
}
418437

419-
if handler != nil {
438+
// Only call handler if we didn't already signal completion via finishReason
439+
if handler != nil && !streamCompleted {
420440
if err := handler("", true); err != nil {
421441
return core.LLMResponse{}, fmt.Errorf("handler error: %w", err)
422442
}

‎pkg/providers/gemini_test.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ func TestGeminiClient_Complete_Success(t *testing.T) {
2121
if !strings.Contains(r.URL.Path, "/v1beta/models/") {
2222
t.Errorf("Expected /v1beta/models/ in path, got %s", r.URL.Path)
2323
}
24-
if !strings.Contains(r.URL.RawQuery, "key=test-key") {
25-
t.Errorf("Expected key=test-key in query, got %s", r.URL.RawQuery)
24+
if r.Header.Get("x-goog-api-key") != "test-key" {
25+
t.Errorf("Expected x-goog-api-key header to be test-key, got %s", r.Header.Get("x-goog-api-key"))
2626
}
2727

2828
var req geminiRequest

‎pkg/sandbox/container_test.go‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ func TestNewExecutorWithAuto(t *testing.T) {
318318
client := NewMockLLMClient()
319319
cfg := DefaultConfig()
320320
cfg.Backend = BackendAuto
321+
cfg.EnableIPC = false // Disable IPC for basic test to avoid network issues in CI
321322

322323
exec, err := New(client, cfg)
323324
if err != nil {
@@ -349,7 +350,7 @@ func TestNewExecutorWithAuto(t *testing.T) {
349350
}
350351

351352
if !strings.Contains(result.Stdout, "Hello from auto backend!") {
352-
t.Errorf("Expected greeting in output, got: %s", result.Stdout)
353+
t.Errorf("Expected greeting in output, got stdout: %q, stderr: %q", result.Stdout, result.Stderr)
353354
}
354355
}
355356

0 commit comments

Comments
 (0)