new iter to get api key
This commit is contained in:
parent
b0395effcf
commit
1555d34a7d
2 changed files with 73 additions and 43 deletions
|
|
@ -88,18 +88,31 @@ func NewAPIKeyPool(keys []string) *APIKeyPool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *APIKeyPool) Get() string {
|
type APIKeyIterator struct {
|
||||||
if len(p.keys) == 0 {
|
pool *APIKeyPool
|
||||||
return ""
|
startIdx uint32
|
||||||
|
attempt uint32
|
||||||
}
|
}
|
||||||
if len(p.keys) == 1 {
|
|
||||||
return p.keys[0]
|
func (p *APIKeyPool) NewIterator() *APIKeyIterator {
|
||||||
|
if len(p.keys) == 0 {
|
||||||
|
return &APIKeyIterator{pool: p}
|
||||||
}
|
}
|
||||||
idx := atomic.AddUint32(&p.current, 1) - 1
|
idx := atomic.AddUint32(&p.current, 1) - 1
|
||||||
if idx >= uint32(len(p.keys))-1 {
|
return &APIKeyIterator{
|
||||||
atomic.CompareAndSwapUint32(&p.current, idx+1, 0)
|
pool: p,
|
||||||
|
startIdx: idx,
|
||||||
}
|
}
|
||||||
return p.keys[idx%uint32(len(p.keys))]
|
}
|
||||||
|
|
||||||
|
func (it *APIKeyIterator) Next() (string, bool) {
|
||||||
|
length := uint32(len(it.pool.keys))
|
||||||
|
if length == 0 || it.attempt >= length {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
key := it.pool.keys[(it.startIdx+it.attempt)%length]
|
||||||
|
it.attempt++
|
||||||
|
return key, true
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchProvider interface {
|
type SearchProvider interface {
|
||||||
|
|
@ -117,13 +130,13 @@ func (p *BraveSearchProvider) Search(ctx context.Context, query string, count in
|
||||||
url.QueryEscape(query), count)
|
url.QueryEscape(query), count)
|
||||||
|
|
||||||
var lastErr error
|
var lastErr error
|
||||||
maxAttempts := len(p.keyPool.keys)
|
iter := p.keyPool.NewIterator()
|
||||||
if maxAttempts == 0 {
|
|
||||||
return "", errors.New("no api key available for Brave")
|
|
||||||
}
|
|
||||||
|
|
||||||
for attempt := 0; attempt < maxAttempts; attempt++ {
|
for {
|
||||||
apiKey := p.keyPool.Get()
|
apiKey, ok := iter.Next()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", searchURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -210,13 +223,13 @@ func (p *TavilySearchProvider) Search(ctx context.Context, query string, count i
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastErr error
|
var lastErr error
|
||||||
maxAttempts := len(p.keyPool.keys)
|
iter := p.keyPool.NewIterator()
|
||||||
if maxAttempts == 0 {
|
|
||||||
return "", errors.New("no api key available for Tavily")
|
|
||||||
}
|
|
||||||
|
|
||||||
for attempt := 0; attempt < maxAttempts; attempt++ {
|
for {
|
||||||
apiKey := p.keyPool.Get()
|
apiKey, ok := iter.Next()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"api_key": apiKey,
|
"api_key": apiKey,
|
||||||
|
|
@ -402,13 +415,13 @@ func (p *PerplexitySearchProvider) Search(ctx context.Context, query string, cou
|
||||||
searchURL := "https://api.perplexity.ai/chat/completions"
|
searchURL := "https://api.perplexity.ai/chat/completions"
|
||||||
|
|
||||||
var lastErr error
|
var lastErr error
|
||||||
maxAttempts := len(p.keyPool.keys)
|
iter := p.keyPool.NewIterator()
|
||||||
if maxAttempts == 0 {
|
|
||||||
return "", errors.New("no api key available for Perplexity")
|
|
||||||
}
|
|
||||||
|
|
||||||
for attempt := 0; attempt < maxAttempts; attempt++ {
|
for {
|
||||||
apiKey := p.keyPool.Get()
|
apiKey, ok := iter.Next()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"model": "sonar",
|
"model": "sonar",
|
||||||
|
|
|
||||||
|
|
@ -695,31 +695,48 @@ func TestAPIKeyPool(t *testing.T) {
|
||||||
t.Fatalf("unexpected keys: %v", pool.keys)
|
t.Fatalf("unexpected keys: %v", pool.keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test Get()
|
// Test Iterator: each iterator should cover all keys exactly once
|
||||||
if k := pool.Get(); k != "key1" {
|
iter := pool.NewIterator()
|
||||||
t.Errorf("expected key1, got %s", k)
|
expected := []string{"key1", "key2", "key3"}
|
||||||
|
for i, want := range expected {
|
||||||
|
k, ok := iter.Next()
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("iter.Next() returned false at step %d", i)
|
||||||
}
|
}
|
||||||
if k := pool.Get(); k != "key2" {
|
if k != want {
|
||||||
t.Errorf("expected key2, got %s", k)
|
t.Errorf("step %d: expected %s, got %s", i, want, k)
|
||||||
}
|
}
|
||||||
if k := pool.Get(); k != "key3" {
|
|
||||||
t.Errorf("expected key3, got %s", k)
|
|
||||||
}
|
}
|
||||||
if k := pool.Get(); k != "key1" {
|
// Should be exhausted
|
||||||
t.Errorf("expected key1, got %s", k)
|
if _, ok := iter.Next(); ok {
|
||||||
|
t.Errorf("expected iterator exhausted after all keys")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Second iterator starts at next position (load balancing)
|
||||||
|
iter2 := pool.NewIterator()
|
||||||
|
k, ok := iter2.Next()
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("iter2.Next() returned false")
|
||||||
|
}
|
||||||
|
if k != "key2" {
|
||||||
|
t.Errorf("expected key2 (round-robin), got %s", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty pool
|
||||||
emptyPool := NewAPIKeyPool([]string{})
|
emptyPool := NewAPIKeyPool([]string{})
|
||||||
if k := emptyPool.Get(); k != "" {
|
emptyIter := emptyPool.NewIterator()
|
||||||
t.Errorf("expected empty string, got %s", k)
|
if _, ok := emptyIter.Next(); ok {
|
||||||
|
t.Errorf("expected false for empty pool")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Single key pool
|
||||||
singlePool := NewAPIKeyPool([]string{"single"})
|
singlePool := NewAPIKeyPool([]string{"single"})
|
||||||
if k := singlePool.Get(); k != "single" {
|
singleIter := singlePool.NewIterator()
|
||||||
t.Errorf("expected single, got %s", k)
|
if k, ok := singleIter.Next(); !ok || k != "single" {
|
||||||
|
t.Errorf("expected single, got %s (ok=%v)", k, ok)
|
||||||
}
|
}
|
||||||
if k := singlePool.Get(); k != "single" {
|
if _, ok := singleIter.Next(); ok {
|
||||||
t.Errorf("expected single, got %s", k)
|
t.Errorf("expected exhausted after single key")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue