@@ -47,6 +47,7 @@ type Options struct {
4747 MaxLen int
4848 CacheDir string // Override cache directory; defaults to judge.CacheDir(skillDir) when empty
4949 Progress ProgressFunc // Optional progress callback; nil means no output
50+ RateLimit int // Max LLM API requests per second; 0 means unlimited
5051}
5152
5253// progress calls the progress callback if set.
@@ -65,6 +66,17 @@ func resolveCacheDir(opts Options, skillDir string) string {
6566 return judge .CacheDir (skillDir )
6667}
6768
69+ // newThrottle returns a function that blocks until the next request is allowed.
70+ // If rps is 0 or negative, the returned function is a no-op.
71+ // The caller must call the returned stop function when done.
72+ func newThrottle (rps int ) (wait func (), stop func ()) {
73+ if rps <= 0 {
74+ return func () {}, func () {}
75+ }
76+ ticker := time .NewTicker (time .Second / time .Duration (rps ))
77+ return func () { <- ticker .C }, ticker .Stop
78+ }
79+
6880// EvaluateSkill scores a skill directory (SKILL.md and/or reference files).
6981func EvaluateSkill (ctx context.Context , dir string , client judge.LLMClient , opts Options ) (* Result , error ) {
7082 result := & Result {SkillDir : dir }
@@ -77,6 +89,9 @@ func EvaluateSkill(ctx context.Context, dir string, client judge.LLMClient, opts
7789 return nil , fmt .Errorf ("loading skill: %w" , err )
7890 }
7991
92+ wait , stop := newThrottle (opts .RateLimit )
93+ defer stop ()
94+
8095 // Score SKILL.md
8196 if ! opts .RefsOnly {
8297 progress (opts , "scoring" , fmt .Sprintf ("%s/SKILL.md" , skillName ))
@@ -94,6 +109,7 @@ func EvaluateSkill(ctx context.Context, dir string, client judge.LLMClient, opts
94109 }
95110
96111 if result .SkillScores == nil {
112+ wait ()
97113 scores , err := judge .ScoreSkill (ctx , s .RawContent , client , opts .MaxLen )
98114 if err != nil {
99115 return nil , fmt .Errorf ("scoring SKILL.md: %w" , err )
@@ -148,6 +164,7 @@ func EvaluateSkill(ctx context.Context, dir string, client judge.LLMClient, opts
148164 }
149165
150166 if refScores == nil {
167+ wait ()
151168 scores , err := judge .ScoreReference (ctx , content , s .Frontmatter .Name , skillDesc , client , opts .MaxLen )
152169 if err != nil {
153170 progress (opts , "error" , fmt .Sprintf ("scoring %s: %v" , name , err ))
@@ -235,6 +252,10 @@ func EvaluateSingleFile(ctx context.Context, absPath string, client judge.LLMCli
235252 }
236253 }
237254
255+ wait , stop := newThrottle (opts .RateLimit )
256+ defer stop ()
257+
258+ wait ()
238259 scores , err := judge .ScoreReference (ctx , string (content ), skillName , s .Frontmatter .Description , client , opts .MaxLen )
239260 if err != nil {
240261 return nil , fmt .Errorf ("scoring %s: %w" , fileName , err )
0 commit comments