feat: AutoZero 通用 Agent 框架(从 AutoGo 五阶段拆分完成)

llm(三协议 Provider)/ session(JSONL 持久化)/ tools(Registry、围栏、
builtin、computer)/ cdp(browser)/ skills / mcp / agent(循环、两级压缩、
编排、审批门控)+ 底座 jsonl/textx/execx/diag/scratch/spill/imagecache。

产品装配代码(config 桥接、schedule 工具、memory/plan/retro/prompt)
留在 AutoGo,宿主接入方式见 MIGRATION.md。
This commit is contained in:
Sakurasan committed 2026-10-26 00:54:46 +08:00
commit d5dac5a6fc
140 files changed
+26497

No files matched your search

+156
View File
@@ -0,0 +1,156 @@
package llm
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
// RetryConfig 配置重试策略。
type RetryConfig struct {
MaxRetries int // 最大重试次数(默认 3)
InitialBackoff time.Duration // 初始退避时间(默认 1s)
MaxBackoff time.Duration // 最大退避时间(默认 30s)
RetryOn []int // 需要重试的 HTTP 状态码(默认 429, 500-509)
}
// DefaultRetryConfig 默认重试配置。
var DefaultRetryConfig = RetryConfig{
MaxRetries: 3,
InitialBackoff: 1 * time.Second,
MaxBackoff: 30 * time.Second,
RetryOn: []int{429, 500, 502, 503, 504},
}
// globalRetryConfig 全局重试配置。
var globalRetryConfig = DefaultRetryConfig
// SetRetryConfig 设置全局重试配置。
func SetRetryConfig(cfg RetryConfig) {
globalRetryConfig = cfg
}
// GetRetryConfig 获取当前重试配置。
func GetRetryConfig() RetryConfig {
return globalRetryConfig
}
// doWithRetry 发送请求并对瞬态错误做指数退避重试(尊重 Retry-After)。
// 只覆盖「响应开始前」的阶段:HTTP 状态检查在 SSE 流开始之前,一旦 200 并
// 开始读流,错误不再重试(增量已经发出去了)。
func doWithRetry(ctx context.Context, client *http.Client, req *http.Request, body []byte) (*http.Response, error) {
return doWithRetryConfig(ctx, client, req, body, globalRetryConfig)
}
// doWithRetryConfig 使用指定配置执行重试。
func doWithRetryConfig(ctx context.Context, client *http.Client, req *http.Request, body []byte, cfg RetryConfig) (*http.Response, error) {
if cfg.MaxRetries <= 0 {
cfg.MaxRetries = 1
}
if cfg.InitialBackoff <= 0 {
cfg.InitialBackoff = 1 * time.Second
}
if cfg.MaxBackoff <= 0 {
cfg.MaxBackoff = 30 * time.Second
}
if len(cfg.RetryOn) == 0 {
cfg.RetryOn = DefaultRetryConfig.RetryOn
}
// 创建状态码查找表
retryOnMap := make(map[int]bool, len(cfg.RetryOn))
for _, code := range cfg.RetryOn {
retryOnMap[code] = true
}
var lastErr error
var lastRetryAfter time.Duration
for attempt := 0; attempt < cfg.MaxRetries; attempt++ {
if attempt > 0 {
wait := time.Duration(1<<(attempt-1)) * cfg.InitialBackoff
if wait > cfg.MaxBackoff {
wait = cfg.MaxBackoff
}
if lastRetryAfter > 0 {
wait = lastRetryAfter
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(wait):
}
}
r := req.Clone(ctx)
r.Body = io.NopCloser(bytes.NewReader(body))
r.ContentLength = int64(len(body))
resp, err := client.Do(r)
if err != nil {
lastErr = err
lastRetryAfter = 0
continue // 网络错误:可重试
}
// 检查是否需要重试
if resp.StatusCode == http.StatusTooManyRequests || retryOnMap[resp.StatusCode] {
lastRetryAfter = parseRetryAfter(resp.Header.Get("Retry-After"))
_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 4096))
resp.Body.Close()
lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
continue
}
return resp, nil // 2xx 与 4xx 一律交还调用方(4xx 不可重试)
}
return nil, fmt.Errorf("after %d attempts: %w", cfg.MaxRetries, lastErr)
}
// isRetryable 检查错误是否可重试。
func isRetryable(err error) bool {
if err == nil {
return false
}
// 网络错误可重试
if _, ok := err.(interface{ Timeout() bool }); ok {
return true
}
// HTTP 错误
errStr := err.Error()
return contains(errStr, "timeout") ||
contains(errStr, "connection refused") ||
contains(errStr, "connection reset") ||
contains(errStr, "EOF")
}
// contains 检查字符串是否包含子串。
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsSubstr(s, substr))
}
func containsSubstr(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
func parseRetryAfter(v string) time.Duration {
if v == "" {
return 0
}
if secs, err := strconv.Atoi(v); err == nil && secs > 0 && secs <= 60 {
return time.Duration(secs) * time.Second
}
return 0
}