437 lines
9.3 KiB
Go
437 lines
9.3 KiB
Go
package batching
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestLLMNewBatcher(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
assert.NotNil(t, batcher)
|
||
|
|
assert.Equal(t, 0, batcher.QueueSize())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMEnqueueRequest(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{
|
||
|
|
ID: "req-1",
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "Generate code",
|
||
|
|
}
|
||
|
|
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
assert.Equal(t, 1, batcher.QueueSize())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMEnqueueMultipleRequests(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
for i := 0; i < 5; i++ {
|
||
|
|
req := &LLMRequest{
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "Prompt",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
}
|
||
|
|
|
||
|
|
assert.Equal(t, 5, batcher.QueueSize())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMAutoFlushOnMaxBatchSize(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(5, 10*time.Second)
|
||
|
|
|
||
|
|
for i := 0; i < 5; i++ {
|
||
|
|
req := &LLMRequest{
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "Prompt",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
}
|
||
|
|
|
||
|
|
assert.Equal(t, 0, batcher.QueueSize())
|
||
|
|
assert.Equal(t, 1, batcher.PendingBatchCount())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMManualFlush(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "Prompt",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
|
||
|
|
batcher.Flush()
|
||
|
|
assert.Equal(t, 0, batcher.QueueSize())
|
||
|
|
assert.Equal(t, 1, batcher.PendingBatchCount())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMGetPendingBatch(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "Prompt",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
batch := batcher.GetPendingBatch()
|
||
|
|
assert.NotNil(t, batch)
|
||
|
|
assert.Equal(t, 1, len(batch.Requests))
|
||
|
|
assert.Equal(t, "implementer", batch.Type)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMMarkBatchExecuting(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{Type: "implementer", Model: "claude-opus", Prompt: "test"}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
batch := batcher.GetPendingBatch()
|
||
|
|
batcher.MarkBatchExecuting(batch.ID)
|
||
|
|
|
||
|
|
updated := batcher.GetBatchByID(batch.ID)
|
||
|
|
assert.Equal(t, "executing", updated.Status)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMMarkBatchCompleted(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{
|
||
|
|
ID: "req-1",
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "test",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
batch := batcher.GetPendingBatch()
|
||
|
|
|
||
|
|
results := map[string]*LLMResult{
|
||
|
|
"req-1": {
|
||
|
|
RequestID: "req-1",
|
||
|
|
Response: "Generated code",
|
||
|
|
TokenCount: 100,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
batcher.MarkBatchCompleted(batch.ID, results)
|
||
|
|
|
||
|
|
executed := batcher.GetExecutedBatches()
|
||
|
|
assert.Equal(t, 1, len(executed))
|
||
|
|
assert.Equal(t, "completed", executed[0].Status)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMMarkBatchFailed(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{
|
||
|
|
ID: "req-1",
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
batch := batcher.GetPendingBatch()
|
||
|
|
testErr := assert.AnError
|
||
|
|
batcher.MarkBatchFailed(batch.ID, testErr)
|
||
|
|
|
||
|
|
failed := batcher.GetBatchByID(batch.ID)
|
||
|
|
assert.Equal(t, "failed", failed.Status)
|
||
|
|
assert.Error(t, failed.Error)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMGroupByTypeAndModel(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(100, 5*time.Second)
|
||
|
|
|
||
|
|
// Add requests of different types
|
||
|
|
for i := 0; i < 3; i++ {
|
||
|
|
req := &LLMRequest{
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "test",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
}
|
||
|
|
|
||
|
|
for i := 0; i < 2; i++ {
|
||
|
|
req := &LLMRequest{
|
||
|
|
Type: "judge",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "test",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
}
|
||
|
|
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
// Should create 2 batches (one for implementer, one for judge)
|
||
|
|
assert.Equal(t, 2, batcher.PendingBatchCount())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMGetStats(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(5, 5*time.Second)
|
||
|
|
|
||
|
|
// Add 10 requests (will create 2 batches)
|
||
|
|
for i := 0; i < 10; i++ {
|
||
|
|
req := &LLMRequest{
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "test",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
}
|
||
|
|
|
||
|
|
stats := batcher.GetStats()
|
||
|
|
assert.Equal(t, 10, stats.TotalRequests)
|
||
|
|
assert.Equal(t, 2, stats.TotalBatches)
|
||
|
|
assert.Equal(t, 5.0, stats.AvgRequestsPerBatch)
|
||
|
|
// 10 requests in 2 batches saves 8 API calls
|
||
|
|
assert.Equal(t, 8, stats.APICallsSaved)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMResultDelivery(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{
|
||
|
|
ID: "req-1",
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "test",
|
||
|
|
ResultCh: make(chan *LLMResult, 1),
|
||
|
|
}
|
||
|
|
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
batch := batcher.GetPendingBatch()
|
||
|
|
|
||
|
|
results := map[string]*LLMResult{
|
||
|
|
"req-1": {
|
||
|
|
RequestID: "req-1",
|
||
|
|
Response: "Response",
|
||
|
|
TokenCount: 50,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
batcher.MarkBatchCompleted(batch.ID, results)
|
||
|
|
|
||
|
|
// Check if result was delivered to channel
|
||
|
|
select {
|
||
|
|
case result := <-req.ResultCh:
|
||
|
|
assert.NotNil(t, result)
|
||
|
|
assert.Equal(t, "Response", result.Response)
|
||
|
|
case <-time.After(1 * time.Second):
|
||
|
|
t.Fatal("Result not delivered")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMMultipleBatches(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(3, 5*time.Second)
|
||
|
|
|
||
|
|
// Create 3 batches (3 requests each)
|
||
|
|
for batch := 0; batch < 3; batch++ {
|
||
|
|
for i := 0; i < 3; i++ {
|
||
|
|
req := &LLMRequest{
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "test",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
assert.Equal(t, 3, batcher.PendingBatchCount())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMQueueSize(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
|
||
|
|
assert.Equal(t, 1, batcher.QueueSize())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMPendingBatchCount(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
assert.Equal(t, 1, batcher.PendingBatchCount())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMGetExecutedBatches(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
for i := 0; i < 2; i++ {
|
||
|
|
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
batch := batcher.GetPendingBatch()
|
||
|
|
batcher.MarkBatchCompleted(batch.ID, make(map[string]*LLMResult))
|
||
|
|
}
|
||
|
|
|
||
|
|
executed := batcher.GetExecutedBatches()
|
||
|
|
assert.Equal(t, 2, len(executed))
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMTimeSinceLastFlush(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
time.Sleep(100 * time.Millisecond)
|
||
|
|
elapsed := batcher.TimeSinceLastFlush()
|
||
|
|
|
||
|
|
assert.Greater(t, elapsed, 50*time.Millisecond)
|
||
|
|
assert.Less(t, elapsed, 200*time.Millisecond)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMShouldFlush(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(100, 100*time.Millisecond)
|
||
|
|
|
||
|
|
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
|
||
|
|
// Should not flush yet
|
||
|
|
assert.False(t, batcher.ShouldFlush())
|
||
|
|
|
||
|
|
// Wait for age to exceed
|
||
|
|
time.Sleep(150 * time.Millisecond)
|
||
|
|
assert.True(t, batcher.ShouldFlush())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMClear(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
batcher.Clear()
|
||
|
|
assert.Equal(t, 0, batcher.QueueSize())
|
||
|
|
assert.Equal(t, 0, batcher.PendingBatchCount())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMGetBatchByID(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
batch := batcher.GetPendingBatch()
|
||
|
|
retrieved := batcher.GetBatchByID(batch.ID)
|
||
|
|
|
||
|
|
assert.NotNil(t, retrieved)
|
||
|
|
assert.Equal(t, batch.ID, retrieved.ID)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMGetBatchInfo(t *testing.T) {
|
||
|
|
batch := &LLMBatch{
|
||
|
|
ID: "batch-1",
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Status: "completed",
|
||
|
|
CreatedAt: time.Now(),
|
||
|
|
}
|
||
|
|
|
||
|
|
info := batch.GetInfo()
|
||
|
|
assert.Equal(t, "batch-1", info["id"])
|
||
|
|
assert.Equal(t, "implementer", info["type"])
|
||
|
|
assert.Equal(t, "claude-opus", info["model"])
|
||
|
|
assert.Equal(t, "completed", info["status"])
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMEnqueueNil(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
batcher.Enqueue(nil)
|
||
|
|
assert.Equal(t, 0, batcher.QueueSize())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMAutoIDGeneration(t *testing.T) {
|
||
|
|
req := &LLMRequest{
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "test",
|
||
|
|
}
|
||
|
|
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
|
||
|
|
assert.NotEmpty(t, req.ID)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLLMTokenCounting(t *testing.T) {
|
||
|
|
batcher := NewLLMBatcher(10, 5*time.Second)
|
||
|
|
|
||
|
|
req := &LLMRequest{
|
||
|
|
ID: "req-1",
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "test",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
batcher.Flush()
|
||
|
|
|
||
|
|
batch := batcher.GetPendingBatch()
|
||
|
|
|
||
|
|
results := map[string]*LLMResult{
|
||
|
|
"req-1": {
|
||
|
|
RequestID: "req-1",
|
||
|
|
Response: "Response",
|
||
|
|
TokenCount: 500,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
batcher.MarkBatchCompleted(batch.ID, results)
|
||
|
|
|
||
|
|
stats := batcher.GetStats()
|
||
|
|
assert.Equal(t, 500, stats.TotalTokens)
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkLLMEnqueue(b *testing.B) {
|
||
|
|
batcher := NewLLMBatcher(1000, 10*time.Second)
|
||
|
|
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
req := &LLMRequest{
|
||
|
|
Type: "implementer",
|
||
|
|
Model: "claude-opus",
|
||
|
|
Prompt: "test",
|
||
|
|
}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkLLMFlush(b *testing.B) {
|
||
|
|
batcher := NewLLMBatcher(1000, 10*time.Second)
|
||
|
|
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
|
||
|
|
batcher.Enqueue(req)
|
||
|
|
|
||
|
|
if (i + 1) % 100 == 0 {
|
||
|
|
batcher.Flush()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|